问题

苹果的文件明确指出:

当属性第一次初始化时,不会调用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总是会被调用。我还没有决定这是否是更好的整体,但它很适合我的情况。


当前回答

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

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

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

其他回答

如果你在初始化式中使用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

你可以用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
    }
}

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

class Classy {

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

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

}

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

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

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

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

不幸的是,根类初始化时不会调用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 }
}

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