Is it possible to allow didSet to be called during initialization in Swift?

IosSwiftDidset

Ios Problem Overview


Question
Apple's docs specify that:

>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.

Is it possible to force these to be called during initialization?

Why?

Let's say I have this class

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        self.someProperty = someProperty
        doStuff()
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

I created the method doStuff, to make the processing calls more concise, but I'd rather just process the property within the didSet function. Is there a way to force this to call during initialization?

Update

I decided to just remove the convenience intializer for my class and force you to set the property after initialization. This allows me to know didSet will always be called. I haven't decided if this is better overall, but it suits my situation well.

Ios Solutions


Solution 1 - Ios

If you use defer inside of an initializer, for updating any optional properties or further updating non-optional properties that you've already initialized and after you've called any super.init() methods, then your willSet, didSet, etc. will be called. I find this to be more convenient than implementing separate methods that you have to keep track of calling in the right places.

For example:

public class MyNewType: NSObject {

    public var myRequiredField:Int

    public var myOptionalField:Float? {
        willSet {
            if let newValue = newValue {
                print("I'm going to change to \(newValue)")
            }
        }
        didSet {
            if let myOptionalField = self.myOptionalField {
                print("Now I'm \(myOptionalField)")
            }
        }
    }
    
    override public init() {
        self.myRequiredField = 1
    
        super.init()
    
        // Non-defered
        self.myOptionalField = 6.28
        
        // Defered
        defer {
            self.myOptionalField = 3.14
        }
    }
}

Will yield:

I'm going to change to 3.14
Now I'm 3.14

Solution 2 - Ios

Create an own set-Method and use it within your init-Method:

class SomeClass {
    var someProperty: AnyObject! {
        didSet {
            //do some Stuff
        }
    }
    
    init(someProperty: AnyObject) {
        setSomeProperty(someProperty)
    }
    
    func setSomeProperty(newValue:AnyObject) {
        self.someProperty = newValue
    }
}

> By declaring someProperty as type: AnyObject! (an implicitly > unwrapped optional), you allow self to fully initialize without > someProperty being set. When you call > setSomeProperty(someProperty) you're calling an equivalent of > self.setSomeProperty(someProperty). Normally you wouldn't be able to > do this because self hasn't been fully initialized. Since > someProperty doesn't require initialization and you are calling a > method dependent on self, Swift leaves the initialization context and > didSet will run.

Solution 3 - Ios

As a variation of Oliver's answer, you could wrap the lines in a closure. Eg:

class Classy {

    var foo: Int! { didSet { doStuff() } }

    init( foo: Int ) {
        // closure invokes didSet
        ({ self.foo = foo })()
    }

}

Edit: Brian Westphal's answer is nicer imho. The nice thing about his is that it hints at the intent.

Solution 4 - Ios

I had the same problem and this works for me

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        defer { self.someProperty = someProperty }
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

Solution 5 - Ios

This works if you do this in a subclass

class Base {

  var someProperty: AnyObject {
    didSet {
      doStuff()
    }
  }

  required init() {
    someProperty = "hello"
  }

  func doStuff() {
    print(someProperty)
  }
}

class SomeClass: Base {

  required init() {
    super.init()

    someProperty = "hello"
  }
}

let a = Base()
let b = SomeClass()

In a example, didSet is not triggered. But in b example, didSet is triggered, because it is in the subclass. It has to do something with what initialization context really means, in this case the superclass did care about that

Solution 6 - Ios

While this isn't a solution, an alternative way of going about it would be using a class constructor:

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            // do stuff
        }
    }

    class func createInstance(someProperty: AnyObject) -> SomeClass {
        let instance = SomeClass() 
        instance.someProperty = someProperty
        return instance
    }  
}

Solution 7 - Ios

In the particular case where you want to invoke willSet or didSet inside init for a property available in your superclass, you can simply assign your super property directly:

override init(frame: CGRect) {
    super.init(frame: frame)
    // this will call `willSet` and `didSet`
    someProperty = super.someProperty
}

Note that Charlesism solution with a closure would always work too in that case. So my solution is just an alternative.

Solution 8 - Ios

unfortunately, didSet observers aren't called when a root class is initialized.

If your class isn't a subclass, you have to use getters and setters to achieve the functionality you want:

class SomeClass {
    private var _test: Int = 0
    var test: Int {
        get { _test }
        set { _test = newValue }
    }
    
    init(test: Int) { self.test = test }
}

alternatively, if your class is a subclass, you can use didSet and do:

override init(test: int) {
    super.init()
    self.test = test
}

The didSet SHOULD get called after super.init() is called.

One thing I have not tried but MIGHT also work:

init(test: int) {
    defer { self.test = test }
}

NOTE: you will need to make your properties nullable, or set a default value for them, or unwrap the class properties.

Solution 9 - Ios

You can solve it in obj-с way:

class SomeClass {
    private var _someProperty: AnyObject!
    var someProperty: AnyObject{
        get{
            return _someProperty
        }
        set{
            _someProperty = newValue
            doStuff()
        }
    }
    init(someProperty: AnyObject) {
        self.someProperty = someProperty
        doStuff()
    }
    
    func doStuff() {
        // do stuff now that someProperty is set
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLoganView Question on Stackoverflow
Solution 1 - IosBrian WestphalView Answer on Stackoverflow
Solution 2 - IosOliverView Answer on Stackoverflow
Solution 3 - Iosoriginal_usernameView Answer on Stackoverflow
Solution 4 - IosCarmine CuofanoView Answer on Stackoverflow
Solution 5 - Iosonmyway133View Answer on Stackoverflow
Solution 6 - IosSnowmanView Answer on Stackoverflow
Solution 7 - IosCœurView Answer on Stackoverflow
Solution 8 - IosXaxxusView Answer on Stackoverflow
Solution 9 - IosyshilovView Answer on Stackoverflow