• 20 hours
  • Medium

Free online content available in this course.

Videos available in this course

Certificate of achievement available at the end this course

Got it!

Expand your properties

Log in or subscribe for free to enjoy all this course has to offer!

Alright, so far we know two categories of properties:

  • instance properties: those that apply to an instance of a class

  • type properties: also known as class or static properties, those apply to the class in general.

Properties of both categories are associated with a value stored in memory. Those are called stored properties.

Sometimes we need to generate a value that depends on some other stored values or some conditions of an object. And we don’t need to store that generated value as it's dynamic and we would have to regenerate it every time one of the conditions it depends on changes. Instead, we can simply calculate it on the fly and present as if it were a normal property.

This variation is called calculated property, and it is the subject of this chapter.

The concept of calculated properties

When would we need something like that?

Say we have a class Square that has a stored property side:

class Square {
    var side = 10.0
}

Say we want to know its perimeter, which is 4 times the length of its side. We could create a normal property for it and reassign a new value to it every time we change the value of the side. We would also have to update the value of the side property every time we change the perimeter value:

class Square {
    var side = 10.0
    var perimeter = 40.0
}

var square = Square()

//changing a value of the side
square.side = 8
square.perimeter = square.side * 4

//changing a value of the perimeter
square.perimeter = 32
square.side = square.perimeter / 4

Needless to say, this is a NO GO!

Instead, we can take advantage of calculated properties. When that property is accessed we'll provide a calculated result based on the current value of the side property.

Getters and setters

Before we dive into the syntax of it, we need to know another new aspect of properties that most of the time happens behind the scenes.

In fact, a property is associated with two actions:

  • get, which is performed when we request a value of that property.

  • set, which is performed when we assign a value to that property.

These actions are also called getter and setter respectively.

Read/write properties

By default, when we declare a property in a simple fashion (like we’ve done so far), that property is assigned both actions: get and set.

This property, in computer terms, is also called a read/write property. That means we can read it (apply the get action)and write to it (apply the set action).

For calculated properties, we must specify those actions explicitly. In order to complete the get action we must 'deliver' the result of calculation. For that we use the  return   keyword - similar to a function.

In fact, getters and setters are functions. Get must always return some value and Set never returns anything

Now we can clarify the syntax for calculated properties:

var calculatedProperty: Type {
    get {
        var result = value
        return result
    }
    set {
        // do somthing
    }
}

The declaration of properties with getters and setters has some similarities to declarations of variables and functions. Like in variable declaration we use the keyword var, the variable name, and specify its type. Then, like function declaration, we use curly brackets to mark the block of code related to the variable. It will have a line of code that returns a value (getters only).

In case of our square perimeter property, the getter would be a simple calculation  side * 4  :

class Square {
    var side = 10.0
    var perimeter: Double {
        get {
            return side * 4
        }
        set {
            // do something
        }
    }
}

We have a very convenient square example, as we can calculate its side by knowing its perimeter (which is one fourth of the perimeter). Our setter would set the side property to this new value. In order to do that we need to access a new value passed to our perimeter property. It's available to us as a  newValue   variable.

Here's the implementation:

class Square {
    var side = 10.0
    
    var perimeter: Double {
        get {
            return side * 4
        }
        set {
            side = newValue / 4
        }
    }
}

Let's test it out:

var square = Square()

print(square.side) // 10.0
print(square.perimeter) // 40.0

// set new side
square.side = 8
print(square.side) // 8.0
print(square.perimeter) // 32.0

// set new perimeter
square.perimeter = 24
print(square.side) // 6.0
print(square.perimeter) // 24.0

Everything works as intended :zorro:!

Read-only properties

Our Square class example conveniently served to demonstrate the basic functionality of calculated properties. What if instead of a Square we had a Rectangle object. It would have to have 2 different sides: length and width. We can still calculate its perimeter. But the inverse calculation makes no sense:

class Rect {
    var length = 2.0
    var width = 6.0
    
    var perimeter: Double {
        get {
            return (length + width) * 2
        }
        set {
            // what am I supposed to do here?
        }
    }
}

Which means we shouldn't allow setting the perimeter value! To guarantee that it never happens, we can simply remove the setter part of our declaration:

var perimeter: Double {
    get {
        return (length + width) * 2
    }
}

Now we can’t write to this property. If we attempt to, we'll get an error:

var rect = Rect()

print(rect.perimeter) // Ok
rect.perimeter = 32 // Error

If a property only implements a getter, it’s called a read-only property (or, a get-only property).

There’s also a shortcut for this. If there's no setter, we no longer need to explicitly specify that it’s a getter. So, within the curly brackets of a variable, we can place the contents of its getter. It tells Swift that there’s only one of the 2 actions supported and by default it’s get:

var perimeter: Double {
    return (length + width) * 2
}

What about the set-only property?

The Set-only property does not exist. A variable must always implement at least a getter. So that, creating an explicit property declaration and not providing any implementation code won't work:

// incorrect declaration
var perimeter: Double {
    // empty block
}

 Is that it?

You know there’s always more!

Property observers

We can do more magic around properties. Like, run more unicorns before or after we set a property - just for fun!

And that can be done by implementing the magic siblings of the set clause:

  • willSet, which gets executed before Set. It provides access to the  newValue   variable.

  • didSet, which gets executed after Set. It provides access to the  oldValue   variable.

So the order of execution would be:

  • willSet

  • set

  • didSet

These are called property observers

Here's how we can use these actions to run Unicorns:

class Square {
    var side: Double = 10.0 {
        willSet {
            print("Run a Unicorn BEFORE setting new value: \(newValue)")
        }
        didSet {
            print("Run a Unicorn AFTER setting new value, can still review old value:  \(oldValue)")
        }
    }
    
    var perimeter: Double {
        get {
            return side * 4
        }
        set {
            side = newValue / 4
        }
    }
}

Let's test it out:

var square = Square()
square.side = 8

And observe the results in the console:

Run a Unicorn BEFORE setting new value: 8.0
Run a Unicorn AFTER setting new value, can still review old value:  10.0

Static calculated properties

We've demonstrated how calculated properties work within an instance. The same resources are available and the same rules apply to Class properties (Static/Type properties). The only difference is that we must accommodate the static property declaration using the static keyword:

class Square {
    // declare a calculated type property
    static var teaser: String {
        get {
            return "I'm about to tell you all about Squares!"
        }
    }
    /* ... */
}

// utilization
print(Square.teaser) // I'm about to tell you all about Squares!

Here's a brief property summary diagram:

Property summary diagram
Property summary diagram

Improving NeXTDestination

Let's refer to our original design:

NeXTDestination object design
NeXTDestination object design

Now let's identify what we haven't addressed - the  amountRemaining  property. And it looks like a good fit for a calculated property. Alter the Adventure class to include amountRemaining as a calculated property. Try to implement it on your own!

// No scrolling yet!

























var amountRemaining: Double {
    return Adventure.totalBudget - Adventure.returnCost - amountSpent
}

An important aspect of this is that it's a read-only property that implements only a getter. We should't allow setting the value of the remaining amount as it depends on other data: budget and return cost, and progressive spending.

Let's list the full implementation of Adventure class:

class Adventure {
    // static properties
    static let maxDestinations = 5
    static let totalBudget = 10000.0
    static let returnCost = 800.0
    
    // properties
    var amountSpent: Double
    var destinations: [Destination]
    var placesVisited: [Destination]
    
    var amountRemaining: Double {
        return Adventure.totalBudget - Adventure.returnCost - amountSpent
    }
    
    // initializer
    init(destinations: [Destination]) {
        self.destinations = destinations
        amountSpent = 0.0
        placesVisited = []
    }
    
    // methods
    func pickDestination() -> Destination? {
        return nil
    }
    
    func tellStory() -> String{
        return ""
    }
}

Another step forward!

Let's Recap!

  • There are two types of properties: stored and calculated properties.

  • A calculated property can be read/write or read-only (a.k.a. get-only).

  • To create a calculated property we must implement a getter and (optionally) a setter.

  • A calculated property is never constant and always has a getter.

  • A stored property can be observed using the willSet and didSet clauses.

Example of certificate of achievement
Example of certificate of achievement