What is the purpose of willSet and didSet in Swift?

224,237

Solution 1

The point seems to be that sometimes, you need a property that has automatic storage and some behavior, for instance to notify other objects that the property just changed. When all you have is get/set, you need another field to hold the value. With willSet and didSet, you can take action when the value is modified without needing another field. For instance, in that example:

class Foo {
    var myProperty: Int = 0 {
        didSet {
            print("The value of myProperty changed from \(oldValue) to \(myProperty)")
        }
    }
}

myProperty prints its old and new value every time it is modified. With just getters and setters, I would need this instead:

class Foo {
    var myPropertyValue: Int = 0
    var myProperty: Int {
        get { return myPropertyValue }
        set {
            print("The value of myProperty changed from \(myPropertyValue) to \(newValue)")
            myPropertyValue = newValue
        }
    }
}

So willSet and didSet represent an economy of a couple of lines, and less noise in the field list.

Solution 2

My understanding is that set and get are for computed properties (no backing from stored properties)

if you are coming from an Objective-C bare in mind that the naming conventions have changed. In Swift an iVar or instance variable is named stored property

Example 1 (read only property) - with warning:

var test : Int {
    get {
        return test
    }
}

This will result in a warning because this results in a recursive function call (the getter calls itself).The warning in this case is "Attempting to modify 'test' within its own getter".

Example 2. Conditional read/write - with warning

var test : Int {
    get {
        return test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        //(prevents same value being set)
        if (aNewValue != test) {
            test = aNewValue
        }
    }
}

Similar problem - you cannot do this as it's recursively calling the setter. Also, note this code will not complain about no initialisers as there is no stored property to initialise.

Example 3. read/write computed property - with backing store

Here is a pattern that allows conditional setting of an actual stored property

//True model data
var _test : Int = 0

var test : Int {
    get {
        return _test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        if (aNewValue != test) {
            _test = aNewValue
        }
    }
}

Note The actual data is called _test (although it could be any data or combination of data) Note also the need to provide an initial value (alternatively you need to use an init method) because _test is actually an instance variable

Example 4. Using will and did set

//True model data
var _test : Int = 0 {

    //First this
    willSet {
        println("Old value is \(_test), new value is \(newValue)")
    }

    //value is set

    //Finaly this
    didSet {
        println("Old value is \(oldValue), new value is \(_test)")
    }
}

var test : Int {
    get {
        return _test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        if (aNewValue != test) {
            _test = aNewValue
        }
    }
}

Here we see willSet and didSet intercepting a change in an actual stored property. This is useful for sending notifications, synchronisation etc... (see example below)

Example 5. Concrete Example - ViewController Container

//Underlying instance variable (would ideally be private)
var _childVC : UIViewController? {
    willSet {
        //REMOVE OLD VC
        println("Property will set")
        if (_childVC != nil) {
            _childVC!.willMoveToParentViewController(nil)
            self.setOverrideTraitCollection(nil, forChildViewController: _childVC)
            _childVC!.view.removeFromSuperview()
            _childVC!.removeFromParentViewController()
        }
        if (newValue) {
            self.addChildViewController(newValue)
        }

    }

    //I can't see a way to 'stop' the value being set to the same controller - hence the computed property

    didSet {
        //ADD NEW VC
        println("Property did set")
        if (_childVC) {
//                var views  = NSDictionaryOfVariableBindings(self.view)    .. NOT YET SUPPORTED (NSDictionary bridging not yet available)

            //Add subviews + constraints
            _childVC!.view.setTranslatesAutoresizingMaskIntoConstraints(false)       //For now - until I add my own constraints
            self.view.addSubview(_childVC!.view)
            let views = ["view" : _childVC!.view] as NSMutableDictionary
            let layoutOpts = NSLayoutFormatOptions(0)
            let lc1 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("|[view]|",  options: layoutOpts, metrics: NSDictionary(), views: views)
            let lc2 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
            self.view.addConstraints(lc1)
            self.view.addConstraints(lc2)

            //Forward messages to child
            _childVC!.didMoveToParentViewController(self)
        }
    }
}


//Computed property - this is the property that must be used to prevent setting the same value twice
//unless there is another way of doing this?
var childVC : UIViewController? {
    get {
        return _childVC
    }
    set(suggestedVC) {
        if (suggestedVC != _childVC) {
            _childVC = suggestedVC
        }
    }
}

Note the use of BOTH computed and stored properties. I've used a computed property to prevent setting the same value twice (to avoid bad things happening!); I've used willSet and didSet to forward notifications to viewControllers (see UIViewController documentation and info on viewController containers)

I hope this helps, and please someone shout if I've made a mistake anywhere here!

Solution 3

You can also use the didSet to set the variable to a different value. This does not cause the observer to be called again as stated in Properties guide. For example, it is useful when you want to limit the value as below:

let minValue = 1

var value = 1 {
    didSet {
        if value < minValue {
            value = minValue
        }
    }
}

value = -10 // value is minValue now.

Solution 4

These are called Property Observers:

Property observers observe and respond to changes in a property’s value. Property observers are called every time a property’s value is set, even if the new value is the same as the property’s current value.

Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/ca/jEUH0.l

I suspect it's to allow for things we would traditionally do with KVO such as data binding with UI elements, or triggering side effects of changing a property, triggering a sync process, background processing, etc, etc.

Solution 5

NOTE

willSet and didSet observers are not called when a property is set in an initializer before delegation takes place

Share:
224,237

Related videos on Youtube

zneak
Author by

zneak

No longer contributing to Stack Overflow.

Updated on July 17, 2020

Comments

  • zneak
    zneak almost 4 years

    Swift has a property declaration syntax very similar to C#'s:

    var foo: Int {
        get { return getFoo() }
        set { setFoo(newValue) }
    }
    

    However, it also has willSet and didSet actions. These are called before and after the setter is called, respectively. What is their purpose, considering that you could just have the same code inside the setter?

    • mfaani
      mfaani about 7 years
      I personally don't like many answers here. They go too much down into the syntax. The differences are more about semantics and code readiblity. Computed Property (get & set) are basically to have a property computed based on another property, e.g. converting a label's text into a year Int. didSet & willSet are there to say...hey this value was set, now let's do this e.g. Our dataSource was updated...so let's reload the tableView so it would include new rows. For another example see dfri's answer on how to call delegates in didSet
    • Mihir Oza
      Mihir Oza almost 3 years
      Easiest answer found in the comment.
  • zneak
    zneak about 10 years
    Are you saying that there is a performance advantage to using willSet and didSet versus equivalent setter code? This seems like a bold claim.
  • eonil
    eonil about 10 years
    @zneak I used wrong word. I am claiming programmer effort, not the processing cost.
  • zneak
    zneak about 10 years
    This is the Swift example. getFoo and setFoo are simple placeholders for whatever you'd like the getters and setters to do. C# doesn't need them either. (I did miss a few syntactical subtleties as I asked before I had access to the compiler.)
  • Analog File
    Analog File about 10 years
    Oh, ok. But the important point is that a computed property does NOT have an underlying storage. See also my other answer: stackoverflow.com/a/24052566/574590
  • Klaas
    Klaas almost 10 years
    Attention: willSet and didSet are not called when you set the property from within an init method as Apple notes: willSet and didSet observers are not called when a property is first initialized. They are only called when the property’s value is set outside of an initialization context.
  • pronebird
    pronebird over 9 years
    Why cant' use I use didSet together with get and set..?
  • Andreas
    Andreas over 9 years
    But they seem to be called on an array property when doing this: myArrayProperty.removeAtIndex(myIndex) ...Not expected.
  • evfemist
    evfemist over 9 years
    //I can't see a way to 'stop' the value being set to the same controller - hence the computed property warning dissapear after I used if let newViewController = _childVC { instead of if (_childVC) {
  • user3675131
    user3675131 over 9 years
    get and set are used to create a computed property. These are purely methods, and there is no backing storage (instance variable). willSet and didSet are for observing changes to stored variable properties. Under the hood, these are backed by storage, but in Swift it's all melded into one.
  • Marmoy
    Marmoy about 8 years
    You can wrap the assignment in a defer { } statement within the initialiser which causes the willSet and didSet methods to be called when the initialiser scope is exited. I'm not necessarily recommending it, just saying that it's possible. One of the consequences is that it only works if you declare the property optional, since it is not strictly being initialised from the initialiser.
  • JW.ZG
    JW.ZG about 8 years
    In your example 5, in get, I think you need to add if _childVC == nil { _childVC = something } and then return _childVC.
  • Vikash Rajput
    Vikash Rajput about 8 years
    Please explain below line. I'm not getting, is this method or variable var propertyChangedListener : (Int, Int) -> Void = { println("The value of myProperty has changed from ($0) to ($1)") }
  • ramazan polat
    ramazan polat almost 8 years
    Initialising properties in same line is NOT supported in Swift 3. You should change the answer to conform swift 3.
  • zneak
    zneak almost 8 years
    @RamazanPOLAT, with that said, this still appears to work on Swift 3. (Press the play button at the bottom to execute it.)
  • ramazan polat
    ramazan polat almost 8 years
    @zneak my bad. Looks like xcode playground messes things up when you have lots of codes in one playground page.
  • eemrah
    eemrah about 7 years
    or using willSet make sense some effects on this outlets methods , isn't it ?
  • Aaron
    Aaron about 6 years
    To make an observer trigger in an init, create a setValue function that makes the assignment to the observed variable, and call that setValue function from the init function, it will execute the observers.