- All posts
- Property observers already give you the old and new value
Property observers already give you the old and new value
willSet and didSet take a parameter, and both have a default name — so naming them explicitly is usually redundant. The rules about when they do not fire matter more.
willSet and didSet each receive the value they are missing: willSet gets
the incoming one, didSet gets the previous one. Both have default names, so
this compiles as written:
var language = "ObjC" {
willSet {
print("about to replace \(language) with \(newValue)")
}
didSet {
print("replaced \(oldValue) with \(language)")
}
}
Each observer reads one value from the parameter and the other from the
property. In willSet the property still holds the old value; in didSet it
already holds the new one.
The explicit form — willSet(newValue), didSet(oldValue) — names the
parameter exactly what it is called by default, so it adds nothing. It is worth
writing only when a different name reads better:
didSet(previousUser) {
guard previousUser.id != user.id else { return }
reload()
}
When they do not run causes more confusion than what the parameters are called. There are three rules.
They do not run during initialization
Assigning to a property from within init does not call its observers,
including through the memberwise initializer. Setup that belongs in didSet
has to be called explicitly at the end of init, or it silently does not
happen for the first value.
They do not run on assignment from inside didSet
Writing to the property in its own didSet does not re-enter the observer,
which is what stops it recursing. So a didSet that clamps or normalises its
own value works, and one written expecting to see its own write come back
around does not.
They run on every write, not every change
Setting a property to the value it already holds still fires both observers. If the body triggers a reload or a layout pass, an equality check is usually what was meant:
didSet {
guard oldValue != items else { return }
tableView.reloadData()
}
The same applies one level down. If the property holds a struct, mutating one
of its fields is a write to the whole property, so settings.fontSize = 14
fires didSet on settings — worth knowing before putting expensive work in
one.