Stored vs computed vs lazy vs property observers (willSet/didSet)?
Predict first
In what order do the lines print?
struct S {
lazy var x: Int = { print("init x"); return 1 }()
}
var s = S()
print("made")
_ = s.x
_ = s.x
- init x, made
- made, init x
- made, init x, init x
Mental model. Stored is a drawer. Computed is a vending machine — you get a value on demand, nothing is kept. lazy is a drawer built on first use.
Stored occupies memory. Computed has get/set and no storage of its own. lazy var is initialized once on first access, must be a var, and is not thread-safe by default.
willSet/didSet fire on a stored property when it is written. The precision that matters: they do not fire for a property initialized inside its own class's init, but they do fire when a subclass assigns a superclass property after super.init(). You cannot have a custom setter and observers on the same property.
lazy runs its initializer on first access, exactly once (on a single thread).
Common traps
- “
didSetnever fires in an initializer.” It does not fire for the property's own initialization, but a subclass assigning a superclass property aftersuper.init()does trigger it. - “
lazyis thread-safe because it runs once.” It runs once per access path under race — two threads can both initialize it. See Q41.
They might follow up with
- Can a
letbe lazy? No —lazyrequires storage that mutates on first access, so it must bevar. - Can a struct have a lazy property? Yes, but any method that touches it becomes
mutating.
Related questions
- 1.6.2 Property observers vs overriding a setter — which do you use?
- 1.6.5 Are property wrappers thread-safe? Is lazy?
Property observers vs overriding a setter — which do you use? →