Reputation: 7163
The Apple documentation states:
The
willSet
anddidSet
observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.
which means if I have some type:
enum State {
case disabled, enabled
}
and some variable that has a willSet
or didSet
observer:
var state: State = .disabled {
willSet {
// do something
}
}
the willSet
observer won't get called until after I explicitly set state
in during or after initialization of that particular instance.
Why does it work this way? As a developer, I would look at the above code and make the assumption, not unreasonably, that the observer block gets called for the original value, irrespective of instance initialization. It seems like one heck of an anti-pattern to have to set state = .disabled
in the initializer to trigger the observer for the initial value.
Upvotes: 2
Views: 1935
Reputation: 32786
Think what would happen in a situation like this:
class Person {
var firstName: String {
didSet {
print("New full name:", firstName, lastName)
}
}
var lastName: String {
didSet {
print("New full name:", firstName, lastName)
}
}
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
You'd end up using an uninitalized value for lastName
. Which can could very well crash the app.
Swift wants to ensure object integrity, and executing observers from init
can't guarantee this, as observers have access to all class members.
Upvotes: 1
Reputation: 17040
The way properties are handled in Swift is roughly analogous to the recommended behavior for initialization in Objective-C, notably the section "Don't Use Accessors in Initializer Methods and Dealloc" found on this page. If you set a property foo
in your init
method, it's equivalent to setting the _foo
instance variable in Objective-C, whereas setting foo
outside of init
is analogous to calling foo
's accessors. Basically, what used to be considered a best practice is now actually enforced by the compiler.
The reason for this is to avoid aberrant side effects caused by accessors assuming that the rest of the object's state is set up already, when in actuality it is not.
This can be worked around fairly easily, though; you can make a fooDidSet()
method, call that from within foo
's didSet
, and then also call it at the end of your initializer after calling super's designated init
. Alternatively, you can just set the property to itself after calling super's init
to cause its didSet
to fire.
Upvotes: 1
Reputation: 56332
There's no restriction on whether willSet
/didSet
can access other properties of the instance. For that reason, all the instance properties need to be properly initialised before any observers are called.
On top of that, if the observer didSet
was called when first setting a property's value, the oldValue
variable would contain garbage, as it would never have been set.
Upvotes: 1
Reputation: 62052
As Hamish's comment points out, in the case of willSet
there's not a valid value that state
could have here (and in the case of didSet
, there's not a valid value the newValue
argument could have).
Upvotes: 2