Lesson 9 - Static class members in Swift
In the previous lesson, Arena with a mage in Swift (inheritance and polymorphism) , we put our knowledge of inheritance and polymorphism to the test.
In today's tutorial, we're going to go over static class members in Swift. Until now, we only used data, states, being carried by an instance. Properties, which we've defined, belonged to an instance and were unique to each instance. OOP, however, allows us to declare properties and methods on a class itself. We call these members static, sometimes called class members, who are independent of an instance.
WARNING! Today's lesson will show you that static members are approaches that actually violate the object-oriented principles. OOP includes them only for special cases and in general everything can be written without static members. We always have to think carefully before adding static members. Generally, I would recommend for you not to use static members ever, unless you're absolutely sure what you're doing. Like global variables (defined outside a class and accessible from the whole module), static members are something that enable you to write bad code and violate good practices. Today, we'll go over this topic just to make you understand some static methods and classes in Swift libraries, not to write your own. Use this knowledge wisely, there will be less evil in the world.
Static (class) properties
We can declare various elements as static, let's start with properties. As I
mentioned in the introduction, static elements belong to a class, not to an
instance. So the data stored there can be read even if an instance of the class
has not been declared. Basically, we could say that static fields are shared
among all class instances, but even that wouldn't be accurate since they aren't
actually related to instances. Let's create a new project, name it something
like Statics
, and create a simple User
class:
class User { private var name: String private var password: String private var loggedIn : Bool init(name: String, password: String) { self.name = name self.password = password loggedIn = false } func logIn(enteredPassword: String) -> Bool { if (enteredPassword == password) { loggedIn = true } else { loggedIn = false } } }
The class is simple, it represents a user in a system. Each user instance has
its own name, password, and carries information about whether the user is logged
in. In order for the user to log-in, we call the logIn()
method
which takes a password as a parameter. The method verifies whether it's the
right password. If the person behind the keyboard is really that user, it logs
him/her in. It returns true
/false
depending on whether
the login was successful. In reality, the password would be also hashed, but we
won't do any hashing this time around.
When a new user registers, the system tells him/her what the minimum length
of their password must be. This number should be stored somewhere.
During user registration, we still wouldn't have its instance.
The object has not been created and would only be created after the form has
been completely filled and submitted. Therefore, we can't use a public
minimalPasswordLength
property in the User
class for
this purpose. Either way, we still need a minimum password length stored in the
User
class somewhere since it, naturally, belongs there. We'll make
this value a static property using the
static
modifier:
class User { private var name : String private var password : String private val loggedIn : Bool static let minimalPasswordLength = 6 // ... }
Now, let's switch to main.swift
and print the property to the
console. We can access this property directly in the class:
print(User.minimalPasswordLength)
We can see the property really belongs to the class. We can access it from different places in the code without having to create a user. Alternatively, on the user instance, we wouldn't be able to access it:
// this code will result in error let u = User(name: "Thomas White", password: "passwordissword") print(u.minimalPasswordLength)
Xcode will report an error and the code won't be compiled.
Numbering the instances
Another practical use of static properties is to assign unique identification
numbers to each user. If we didn't know what static members were, we'd have to
check every user creation and increment a counter. Instead, we'll create a
private static property nextId
right on the User
class. The next user who registers will have their id
stored there.
The first user's id
will be 1
, the second
2
, and so on. The User
will get a new property -
id
, that will be set in the constructor depending on what the
nextId
value is:
class User { private var name : String private var password : String private var loggedIn : Bool private let id : Int static let minimalPasswordLength = 6 private static var nextId = 1 init(name : String, password : String) { self.name = name self.password = password loggedIn = false self.id = User.nextId User.nextId += 1 } // ... }
The class stores the next instance's id
by itself. We assign
this id
to a new instance in the constructor and increase it by
1
, which prepares it for the next instance. Not only properties can
be static, this approach could be applied to a variety of class members.
Static methods
Static methods are called on the class. All they usually are is
utilities that we need to use often and creating an instance
every time would be counterproductive. Swift does support functions which aren't
called on an instance nor class (this is how we use the print()
function), however, it's much more useful to group related functions into
classes. That way, Xcode will hint them to us better and we'll be able to open
the list of everything we can call on the particular class by pressing
Ctrl + Space.
Let's make another example, just to clarify. During user registration, we
need to know the minimum password length before we create its instance. Also, it
would be great if we could check the password before the program creates the
user. A full check, in this case, would include checking whether the length is
correct, making sure it doesn't contain accent characters, verifying that there
is at least one number in it, and so on. To do this, we'll create a static
validatePassword()
method on the User
class:
static func validatePassword(_ password: String) -> Bool { if password.count >= User.minimalPasswordLength { // password validation code (omitted for simplification purposes) return true } return false }
Let's try to call the method on the User
class:
print(User.validatePassword("passwordissword"))
Viewer beware! The validatePassword()
method belongs to
the class. Meaning that we can't access any instance properties in it.
Those properties don't exist in the class context, rather in the context of an
instance. It wouldn't make any sense to use the user's name
in our
method! You can try it out if you'd like to get a visual confirmation of its
impossibility.
Password validation can be achieved without knowledge of static members. We
could create a UserValidator
class and write methods in it
accordingly. Then, we'd have to create its instance to be able to call those
methods. It'd be a bit confusing because the "concept of a user" would be
unnecessarily split up into two classes. Now, thanks to static members, all of
the information is neatly organized in one place.
Let's add another method to read the user id
from outside the
class.
func returnId() -> Int { return id }
Finally, we'll test our methods. main.swift
will look like
this:
let u = User(name: "Thomas White", password: "passwordissword") print("First user's ID: \(u.id)") let v = User(name: "Oli Pickle", password: "csfd1fg") print("Second user's ID: \(v.id)") print("Minimum password length: \(User.minimalPasswordLength)") print("Password validation \(User.validatePassword("password"))")
The output will be:
First user's ID: 1 Second user's ID: 2 Minimum password length: 6 Password validation "password": false
Static registry
Let's create a simple static class. We could create a class containing only utility methods and properties. However, I've decided I'll have us create a static registry. Which is basically how you share important data between classes without making an instance.
Let's say we're making a more robust application, e.g. a digital journal. The application would be multilingual, its features would include adding bookmarks, choosing folders for storing files, color schemes and even running at system startup. It would have adjustable settings, e.g. picking the first day of the week (Sunday/Monday), which would be accessed from various parts in the code. Without knowledge of static members, we'd have to pass the settings instance to all objects (calendar, tasks, notes...) through the constructor.
One workaround would be to use a static class to store these settings. It would be accessible everywhere in the program, without even having to create an instance. It would contain all the necessary settings that would be used by objects. Our static class would end up looking something like this:
class Settings { private static var language = "ENG" private static var colorScheme = "red" private static var runAtStartUp = true static func Language() -> String { return language } static func ColorScheme() -> String { return colorScheme } static func RunAtStartup() -> Bool { return runAtStartUp } }
All the properties and methods are marked with the static
modifier. I intentionally didn't include any public properties, I just created
getter methods so that the properties cannot be changed. It's a little bit
uncomfortable for the programmer, next time we'll show how to do this better and
declare read-only properties.
Now we'll put our class to use even though we don't have a full journal
application. Let's make a Calendar
class and verify that we can
truly access the Settings
there. We'll add a method to it that
returns all of the settings:
class Calendar { func getSettings() -> String { var s = "" s += "Language: \(Settings.Language())\n" s += "Color scheme: \(Settings.colorScheme())\n" s += "Run at start-up: \(Settings.runAtStartUp())" return s } }
Last of all, print it all to the console:
let calendar = Calendar()
print(calendar.getSettings())
The output:
Language: ENG Color scheme: red Run at start-up: true
We see that calendar instance has no problem accessing all program settings.
Again, be careful, this code can be improperly used to share unencapsulated data. We must only use it in specific situations. Most data exchanges should happen using parameterized instance constructors, not through static members.
Static members appear very often in design patterns, we've mentioned them in the last lesson as well. We'll get in further detail in the approaches that bring object-oriented programming to perfection later on. For now, this will do. I don't want to overwhelm you
In the next lesson, Properties in Swift, we'll look at how to declare advanced properties in Swift.