Computed properties and observers
Picture a form Rect with the fields "width" and "height". Below them is a third field, "area", but there is nothing to write into it: next to it is a printed rule, "width × height". Whoever reads the form multiplies the two numbers and always gets a fresh answer.
If the area were written in by hand, it would go stale after the width was corrected. A rule in place of a number protects you from that mistake. In Swift, a field with a rule is written like this:
struct Rect {
var width: Double
var height: Double
var area: Double {
width * height
}
}
var r = Rect(width: 2, height: 3)
print(r.area) // 6.0
r.width = 4
print(r.area) // 12.0Let's walk through the program step by step:
widthandheightare ordinary properties that hold data.areahas braces with an expression after its type: this is a rule, not data.Rect(width: 2, height: 3)fills in only the width and the height: there is no value forareain the parentheses.- Reading
r.arearuns the rule, and the program prints6.0. - After
r.width = 4, the same readr.areaalready gives12.0.
Stored and computed properties
Properties that keep their value in the instance itself are called stored properties. Every struct property before this lesson was one. And area is a computed property, as with enums: it stores nothing and works out its value again by its rule on every read.
How a computed property is declared:
- always with
var, even if it is only read: withletthe compiler answers'let' declarations cannot be computed properties; - the type is required:
var area: Double; - the type is followed by a body in braces, as in a function, and a single expression in it is returned without
return.
You could get the area from a method func area() -> Double too. But a computed property reads like data, without parentheses: r.area. You choose it when the value describes the instance itself rather than an action.
Property observers
Sometimes something has to happen on every change of a property: log the change or report it. Picture a stamp on a field of the form: "when you correct this, note in the margin what it was and what it became". In Swift such a stamp is called a property observer. You write it in braces after the declaration of a stored property.
didSet
didSet runs right after a new value has been written into the property:
struct Thermostat {
var degrees = 20 {
didSet {
print("was \(oldValue), now \(degrees)")
}
}
}
var t = Thermostat(degrees: 18)
t.degrees += 2 // was 18, now 20
t.degrees = 25 // was 20, now 25
print(t.degrees) // 25Let's see when the observer runs:
Thermostat(degrees: 18)creates an instance, and the observer stays silent: while an instance is being created, its properties are filled in without observers.t.degrees += 2writes a new value into the property, anddidSetprintswas 18, now 20. A write through+=is a write too.- Inside
didSetthe propertydegreesalready holds the new value, and the previous one is available under the nameoldValue. - After
t.degrees = 25,oldValueholds20—the value before this write, not the initial18.
willSet
willSet runs before the write. At that moment the property still holds the old value, and the new one is available under the name newValue:
struct Battery {
var level = 100 {
willSet {
print("\(level) -> \(newValue)")
}
}
}
var phone = Battery()
phone.level = 80 // 100 -> 80phone.level = 80 prints 100 -> 80: inside willSet the property level is still 100. One property can have both observers at once.
Here the stamp picture is inaccurate. A person may miss the stamp or skip noting a correction that changes nothing. Swift runs the observer on every write, even when the new value equals the old one.
Common mistake
A value is written into a computed property:
r.area = 10 // ✗error: cannot assign to property: 'area' is a get-only propertyThe program does not compile. area is a rule, not a field with data, and there is nowhere to write to. With the words get-only the compiler names a property that can only be read. To get a different area, change the stored properties width and height.
Key points
- A stored property keeps its value in the instance, while a computed one stores nothing and works out its value on every read.
- A computed property is declared with
var, a type and a body in braces; you cannot write a value into it. willSetruns before the write and seesnewValue;didSetruns after the write and seesoldValue.- Observers stay silent while an instance is being created and run on every write, including one through
+=.