Pre-Christmas sale Front-end week
Christmas are almost here! Get up to 20 % extra points for free! More info
Save up to 80 % on HTML & CSS + JavaScript e-learning! Only this week!

Lesson 10 - Properties in Swift

In the previous lesson, Static class members in Swift, we learned about static members. In today's Swift tutorial, we're going to look at the promised advanced property declaration.

Getters and setters

We often want to have control over how an object property changes from outside of the class. We could set the property as read-only or react to its changes somehow. Let's create a new project (name it Properties) and add the following Student class which will represent a student in a database.

class Student: CustomStringConvertible {
    var name: String
    var male: Boolean
    var age: Int
    var fullAged: Bool

    init(name: String, male: Bool, age: Int) {
        self.name = name
        self.male = male
        self.age = age
        fullAged = true
        if age < 18 {
            fullAged = false
        }
    }

    var description: String {
        var iAmFullAged = "I'm"
        if (!fullAged) {
            iAmFullAged = "I'm not"
        }
        var gender = "male"
        if (!male) {
            gender = "woman"
        }
        return "I'm \(name), \(gender). I'm \(age) years old and \(iAmFullAged) of age."
    }
}

The class is very simple, the student has a name, gender, and age. The fullAged property is set according to the age and provides a more comfortable determination whether the student is of age from different places in the system. We'll use a Bool value to store gender, true indicates that he's male. The constructor will determine whether the student is of age or not. The description property has been altered to suit our needs. In a real world situation, it would probably just return the student's name. Let's create a student using the constructor:

let s = Student(name: "Peter Brown", male: true, age: 20)
print(s)

The output:

I'm Peter Brown, male. I'm 20 years old and I'm of age.

Everything looks nice, but the properties are able to be re-written. We can break the object like this, so it'll no longer function properly (i.e. would have an inconsistent internal state):

let s = Student(name: "Peter Brown", male: true, age: 20)
s.age = 15
s.male = false
print(s)

The output:

I'm Peter Brown, female. I'm 15 years old and I'm of age.

Certainly, we would want fullAged to be updated when the age is changed. Aside from that, there is no need to allow e.g. a gender to be changed. However, we want to keep the properties accessible for reading, so we can't make them private. In earlier lessons of our course, we've used get methods to read private properties. We would name them something like getAge() and so on. To read particular properties, we'll need such methods as soon as we mark them private, so they can't be modified from outside. The class would now look something like this (I omitted the constructor and description):

class Student {
    var name: String
    var male: Bool
    var age: Int
    var fullAged: Bool

    // ...

    func getName() -> String {
        return name
    }

    func getFullAged() -> Bool {
        return fullAged
    }

    func getAge() -> Int {
        return age
    }

    func isMale() -> Bool {
        return male
    }

    func setAge(value: Int) {
        age = value
        // updating whether the student is of age
        fullAged = true
        if age < 18 {
            fullAged = false
        }
    }
}

The methods just returning a value are very simple. In the method setting the age is some more logic, since we have to reconsider the fullAged property. We made sure we can't set variables in any other way than what we want. We now control all the property changes and can work with them as needed. Our design must prevent all unwanted changes of the internal state that would cause an object to malfunction.

Methods for returning values are called getters and methods for setting values are called setters. We could potentially add an editStudent() method to edit other properties sort of like the constructor. Essentially, a student's name, age, and other properties would be updated using this method. We could also validate values being set there since we would be able to handle all attempts to change certain values in one place.

However, asking for properties using methods takes time and could be confusing. That's why Swift implemented other syntax as well.

private(set)

If we only want to prevent setting the properties from outside, we can use the private(set) access modifier. We just place it before the properties of the class, keeping complete encapsulation. Since we won't need the getters (methods) for these properties anymore, we can delete them now.

private(set) var name: String
private(set) var male: Bool
private(set) Var age: Int

The properties act normally inside the class, however, we can't change them from outside now.

Computed properties

We elegantly prevented unwanted changes in properties of our class from outside. Using a different technique, we can make a property to behave as a method, but when working with it, we won't need to write the parentheses () after its name. We can put this to use when we have properties that return a value depending on other properties. In Swift, we call such properties Computed properties. In our class, we'll use it with fullAged where, instead of the stored Bool value, we'll return directly the age < 18 expression. This way the property value will always be updated. The description property which we've already used without fully understanding it works in a similar manner. Let's modify the fullAged property:

var fullAged: Boolean {
    return age >= 18
}

After all modifications, the class will look like this:

class Student: CustomStringConvertible {

    private(set) var name: String
    private(set) var male: Bool
    private(set) var age: Int
    var fullAged: Bool {
        return age >= 18
    }

    init(name: String, male: Bool, age: Int) {
        self.name = name
        self.male = male
        self.age = age
    }

    var description: String {
        var iAmFullAged = "I'm"
        if (!fullAged) {
            iAmFullAged = "I'm not"
        }
        var gender = "male"
        if (!male) {
            gender = "female"
        }
        return "I'm \(name), \(gender). I'm \(age) and \(iAmFullAged) of age."
    }
}

We've finished a simple Swift getter that looks as a normal property, but it's real-only. Better variable name would be isOfAge corresponding to the conventions for Bool values, but we're talking about properties in general here :-)

Of course, if you had a more complex calculation in your getter and access it frequently, it'd be better to think about optimization and re-calculate the value only when the property it depends on changes. In our case, it's the age property.

It would look like this:

private var _age: Int
private(set) var fullAged: Bool

var age: Int {
    get {
        return _age
    }
    set {
        fullAged = newValue >= 18
        _age = newValue
    }
}

In this case we have to create a _age variable which stores the age itself. When setting the value, we also calculate whether the student is of age. This approach is rarely used and exists exclusively for computed properties. newValue represents the new value being set, as you probably guessed.

Observing property changes

It's often useful to be able to react to changes in properties. We could, for example, want to recalculate other properties, call a method if some limit has been exceeded, or just update the user interface.

Swift allows us to react to assigning of new values to a property easily. It's called property observers. We are provided with the willSet and didSet keywords. We write them in curly-bracket blocks as getters and setters. Their names basically tell us what they do. The willSet block is called just before setting a new value to a property. didSet is called after the assignment. You probably won't use both at the same time ever, didSet is used a little more often.

Let's have the same example with age:

private(set) var fullAged: Bool

private(set) var age : Int {
    didSet {
        fullAged = age >= 18
    }
}

Once the age is set, the fullAged property is also recalculated. In this case, traditional setters and getters really weren't the best choice. It's up to you whether you like the didSet solution or the fullAged property as just a simple read-only getter. When dealing with didSet and` willSet`, just keep in mind that they are unfortunately not called if the value is set from the constructor, so in our case we have to set the age of the student for the first time by ourselves in the constructor.

We can test setting the age and fullyAged properties. Let's add a simple method:

func setAge(_ age: Int) {
    self.age = age
}

And edit main.swift:

let s = Student(name: "Peter Brown", male: true, age: 20)
print(s)
s.setAge(17)
print(s)

The output:

I'm Peter Brown, male. I'm 20 years old and I'm of age.
I'm Peter Brown, male. I'm 17 years old and I'm not of age.

We'll use such properties from now on, because they provide us perfect encapsulation. All class variables are basically properties for Swift, it just depends on how they are implemented. Don't forget the very useful and simple private(set) modifier. :-)

In the next lesson, Protocols (interfaces) in Swift, we'll look at how to work with protocols in Swift.


 

 

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!