252

问题

苹果的文档指定:

首次初始化属性时不会调用 willSet 和 didSet 观察者。仅当属性的值在初始化上下文之外设置时才调用它们。

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

为什么?

假设我有这门课

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

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

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

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

更新

我决定只删除我的类的便利初始化程序,并强制您在初始化后设置属性。这让我知道didSet将永远被调用。我还没有决定这是否总体上更好,但它很适合我的情况。

4

9 回答 9

350

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

例如:

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
于 2015-11-29T05:54:22.080 回答
118

创建一个自己的 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
    }
}

通过声明someProperty为 type: AnyObject!(隐式展开的可选),您允许 self 完全初始化而不 someProperty被设置。当您调用时, setSomeProperty(someProperty)您调用的是 self.setSomeProperty(someProperty). 通常你不能这样做,因为 self 还没有完全初始化。由于 someProperty不需要初始化并且您正在调用依赖于 self 的方法,因此 Swift 会离开初始化上下文并且 didSet 将运行。

于 2014-08-10T17:10:44.013 回答
82

作为奥利弗答案的变体,您可以将这些行包装在一个闭包中。例如:

class Classy {

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

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

}

编辑:Brian Westphal 的回答更好恕我直言。关于他的好处是它暗示了意图。

于 2015-04-07T21:51:01.280 回答
12

我有同样的问题,这对我有用

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

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

    func doStuff() {
        // do stuff now that someProperty is set
    }
}
于 2017-07-25T23:23:37.907 回答
2

如果您在子类中执行此操作,则此方法有效

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

例如adidSet不触发。但是在b例子中,didSet是被触发的,因为它在子类中。它必须与initialization context真正的含义有关,在这种情况下,superclass确实关心

于 2016-05-12T09:15:24.980 回答
1

在您想要调用willSetdidSetinit超类中可用的属性的特定情况下,您可以简单地直接分配您的超属性:

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

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

于 2017-03-23T03:00:29.800 回答
1

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

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

    class func createInstance(someProperty: AnyObject) -> SomeClass {
        let instance = SomeClass() 
        instance.someProperty = someProperty
        return instance
    }  
}
于 2015-12-11T21:48:02.587 回答
0

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

注意:您需要使您的属性可以为空,或者为它们设置一个默认值,或者解开类属性。

于 2020-07-06T18:45:55.573 回答
-1

你可以用 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
    }
}
于 2015-09-02T16:50:36.833 回答