Lesson 9 - Static class members in Swift

Swift OOP 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.

Beware of static membersWARNING! 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.


 

 

Article has been written for you by Filip Němeček
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
Activities (3)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!