barndog
barndog

Reputation: 7163

Swift property observers, initial value

The Apple documentation states:

The willSet and didSet 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

Answers (4)

Cristik
Cristik

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

Charles Srstka
Charles Srstka

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

Cezar
Cezar

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

nhgrif
nhgrif

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

Related Questions