是否可以允许在Swift初始化期间调用didSet?


217

苹果的文档指定:

首次初始化属性时,不会调用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将永远被调用。我还不确定总体上是否更好,但这很适合我的情况。


老实说,最好是“接受这就是迅速的运作方式”。如果将内联初始化值放在var语句上,则当然不会调用“ didSet”。这就是为什么“ init()”情况也不能调用“ didSet”的原因。这一切都说得通。
Fattie

@Logan问题本身回答了我的问题;)谢谢!
AamirR

2
如果要使用便捷初始化程序,可以将其与defer以下各项结合使用:convenience init(someProperty: AnyObject) { self.init() defer { self.someProperty = someProperty }
DarekCieśla18-10-25

Answers:


100

创建一个自己的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将运行。


3
为什么这与init中的self.someProperty = newValue不同?您有一个有效的测试用例吗?
mmmmmm 2014年

2
不知道它为什么起作用-但确实如此。在init()-Method中直接设置新值不会调用didSet(),但是使用给定的方法setSomeProperty()可以。
奥利弗2014年

50
我想我知道这里发生了什么。通过声明someProperty为type :(AnyObject!一个隐式解包的可选),您可以self完全初始化而无需someProperty设置。打电话时,setSomeProperty(someProperty)您叫的等价物self.setSomeProperty(someProperty)。通常,由于self尚未完全初始化,因此您将无法执行此操作。由于someProperty不需要初始化,而您正在调用依赖于的方法self,因此Swift会离开初始化上下文并didSet运行。
Logan

3
看起来任何在实际的init()之外设置的东西都不会调用didSet。从字面上看,任何未设置的东西都init() { *HERE* }将调用didSet。这个问题很好,但是使用辅助函数设置一些值会导致didSet被调用。如果您想重用该setter函数,那就不好了。
WCByrne 2015年

4
@Mark很认真,尝试将常识或逻辑应用于Swift只是荒谬的。但是您可以信任投票,也可以自己尝试。我只是做到了而已。是的,这根本没有意义。
Dan Rosenstark '16

305

如果您使用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

6
厚脸皮的小把戏,我喜欢。。。
克里斯·哈顿

4
大。您发现了使用的另一个有用案例defer。谢谢。
瑞安

1
大量使用延期!希望我能给您多个赞成票。
Christian Schnorr

3
非常有趣..我之前从未见过使用过defer。雄辩而有效。
aBikis

但是,您两次设置了myOptionalField,从性能的角度来看,这并不是一个很好的选择。如果进行大量计算该怎么办?
Yakiv Kovalskyi '18年

77

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

class Classy {

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

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

}

编辑:布莱恩·韦斯特法尔的答案是更好的恕我直言。关于他的好处是,它暗示了这个意图。


2
这很聪明,不会用冗余方法污染类。
在于

好答案,谢谢!值得注意的是,在Swift 2.2中,您需要始终将闭包括在括号中。
Max

@Max欢呼声中,样品包括括号现在
original_username

2
聪明的答案!注意:如果您的类是其他子类的子类,则需要在({self.foo = foo})()
kcstricks

1
@Hlung,我没有感觉到将来会比其他任何东西更容易崩溃,但是仍然存在一个问题,即如果不注释代码,那么它的作用还不是很清楚。至少Brian的“ defer”答案给读者一个暗示,我们要在初始化后执行代码。
original_username

10

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

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

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

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

你在哪里得到它?
Vanya

3
这种方法不再有效。编译器抛出两个错误:–在初始化所有存储的属性之前在方法调用“ $ defer”中使用的“ self” –从初始化程序返回而不初始化所有存储的属性
Edward B

没错,但是有一种解决方法。您只需要将someProperty声明为隐式解包的或可选的,并将其提供为默认值且没有nil合并以避免失败。tldr; someProperty必须为可选类型
标记

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

a例子中,didSet不会被触发。但是在b示例中,didSet因为它在子类中,所以被触发了。它必须与initialization context真正含义做些事情,在这种情况下,superclass确实关心


1

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

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

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

1
此解决方案将需要您更改的可选性,someProperty因为在初始化期间不会给它赋值。
米克·麦卡勒姆

1

在特定情况下,您想为超类中的可用属性调用willSetdidSet内部调用init,您可以直接直接分配您的超级属性:

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

请注意,带有闭包的Charlesism解决方案在这种情况下也总是可行的。所以我的解决方案只是一个替代方案。


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

1
@yshilov,您好,我认为这并不能真正解决我一直以来希望解决的问题,因为从技术上讲,当您调用self.someProperty时要离开初始化范围。我相信通过做可以完成同样的事情var someProperty: AnyObject! = nil { didSet { ... } }。不过,有些人可能会喜欢它作为一种变通方法,感谢您的添加
Logan 2015年

@Logan nope,那是行不通的。在init()中didSet将被完全忽略。
yshilov
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.