Computed properties and observers

Swift · Practical · 10 min

Properties are how Swift types expose state; observers and computed values replace most getter and setter methods.

Stored properties hold values. Computed properties calculate a value each time they are read, with an optional setter:

var area: Double { width * height }

A read-only computed property can omit get. With a setter, the new value arrives as newValue.

Property observers run code around changes to a stored property: willSet sees newValue before the change, didSet sees oldValue after it. They're handy for validation, logging or updating dependent state. Observers do not fire during the type's own initializer.

Lazy properties (lazy var) aren't computed until first accessed — useful for expensive setup that may not be needed. They must be var, and lazy initialization is not thread-safe.

Type properties use static (or class for overridable computed ones in classes).

Access control applies separately to the getter and setter: private(set) var count = 0 can be read from anywhere the type is visible but written only inside it. This is the idiomatic way to expose read-only state.

Computed properties can also be throws or async (read-only ones), which lets a property call failable or asynchronous work: var avatar: Image { get async throws { ... } }.

Prefer a computed property over a method when the value is cheap, has no side effects and reads like an attribute.

Example

struct Temperature {
    var celsius: Double {
        didSet { print("changed from \(oldValue) to \(celsius)") }
    }
    var fahrenheit: Double {
        get { celsius * 9 / 5 + 32 }
        set { celsius = (newValue - 32) * 5 / 9 }
    }
}

var t = Temperature(celsius: 20)   // no didSet during init
print(t.fahrenheit)                // 68.0
t.fahrenheit = 212                 // changed from 20.0 to 100.0

final class Counter {
    private(set) var value = 0
    lazy var formatter: String = { print("built once"); return "#" }()
    func tick() { value += 1 }
}
let c = Counter()
c.tick()
print(c.value)                     // 1
// c.value = 5                     // error: setter is private

Remember

  • Computed properties calculate on each read; observers react to writes
  • private(set) exposes read-only state
  • lazy var defers expensive setup until first use

Watch out for

  • Doing heavy work in a computed property that callers assume is cheap.

Check yourself

  1. When does a `didSet` observer NOT run?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/