问题

苹果的文件明确指出:

当属性第一次初始化时,不会调用willSet和didSet观察器。只有当属性的值在初始化上下文之外设置时才会调用它们。

是否有可能在初始化期间强制调用这些?

Why?

假设我有这样一个类

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

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

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

我创建了方法doStuff,以使处理调用更简洁,但我宁愿只处理didSet函数中的属性。有没有办法在初始化期间强制调用这个?

更新

我决定删除类的方便初始化器,并强制您在初始化后设置属性。这让我知道didSet总是会被调用。我还没有决定这是否是更好的整体,但它很适合我的情况。


创建一个自己的set-Method,并在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.


作为Oliver回答的一种变体,您可以将这些行包装成闭包。例如:

class Classy {

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

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

}

编辑:Brian Westphal的回答更好。这句话的好处是暗示了他的意图。


你可以用obj-с的方法来解决:

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

如果你在初始化式中使用defer,用于更新任何可选属性或进一步更新你已经初始化的非可选属性,并且在你调用了任何super.init()方法之后,那么你的willSet, didSet等将被调用。我发现这比实现单独的方法更方便,你必须在正确的地方跟踪调用。

例如:

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

将收益率:

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

虽然这不是一个解决方案,但另一种方法是使用类构造函数:

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

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

如果你在子类中这样做,这是可行的

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()

在本例中,没有触发didSet。但是在b的例子中,didSet被触发了,因为它在子类中。它必须处理初始化上下文的真正含义,在这种情况下,超类确实关心这个


在特定的情况下,你想在init中调用willSet或didSet来获取父类中可用的属性,你可以简单地直接分配你的父属性:

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

注意,带闭包的Charlesism解决方案在这种情况下也总是有效的。所以我的解只是一种选择。


我也有同样的问题,这个方法对我很有效

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

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

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

不幸的是,根类初始化时不会调用didSet观察器。

如果你的类不是子类,你必须使用getter和setter来实现你想要的功能:

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

或者,如果你的类是一个子类,你可以使用didSet并执行:

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

在super.init()被调用之后,didSet应该被调用。

有一件事我没有尝试过,但可能也有用:

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

注意:你需要让你的属性为空,或者为它们设置一个默认值,或者打开类属性。