Property observers vs overriding a setter — which do you use?
Predict first
What does this print?
final class Model {
var value = 0 {
didSet { print("didSet") }
}
init() { value = 1 }
}
_ = Model()
- didSet
- Nothing
Mental model. Observers are a motion sensor on the drawer. Overriding the setter is replacing the drawer’s lock.
Observers react to changes on that type's storage without taking over the write. Use them for logging, invalidation, or keeping a derived value in sync.
Overriding a setter gives you the write path itself — you decide what actually gets stored. It is class-only and inheritance-based. For structs there is no override, so you use observers or a mutating method.
In SwiftUI, @State and @Published already own the write path. Fighting them with observers usually means you should be using onChange or a derived binding instead.
Observers do not fire for assignments in the type’s own initializer. They do fire for a subclass init or any later assignment.
Common traps
- Putting side effects in
didSetthat mutate the same property. You can recurse and hang; guard or use a private backing store. - Assuming observers fire for in-place mutation of a struct property. Mutating a nested struct triggers
didSeton the outer property; mutating a class property does not notify the outer one.
They might follow up with
- What is KVO and how does it relate? Objective-C runtime notification of property changes; Swift
@objc dynamicproperties participate. Legacy, and easy to crash. - Why does SwiftUI not use observers for state? It uses the wrapper/macro to know what to invalidate, not property observers.
Related questions
- 1.6.1 Stored vs computed vs lazy vs property observers (willSet/didSet)?
- 1.6.3 What is a property wrapper? Can you write a custom one?
- 1.6.5 Are property wrappers thread-safe? Is lazy?
← Stored vs computed vs lazy vs property observers (willSet/didSet)? What is a property wrapper? Can you write a custom one? →