13

当重写属性的 didSet 观察者导致递归时,为什么?

class TwiceInt {
    var value:Int  = 0 {
        didSet {
            value *= 2
        }
    }
}

class QuadInt : TwiceInt {
    override var value:Int {
        didSet {
            value *= 4
        }
    }
}

let t = TwiceInt()
t.value = 5 // this works fine


let q = QuadInt()
q.value = 5 // this ends up in recursion

如果我QuadInt更新

class QuadInt : TwiceInt {
    override var value:Int {
        didSet {
            super.value *= 4
        }
    }
}

q.value = 5 // q.value = 80

所以我猜这个电话是这样的:

value = 5
QuadInt:didSet ( value *= 4 )
value = 20
TwiceInt:didSet ( value *= 2 )
value = 40
TwiceInt:didSet ( value *= 2 )
value = 80

这或多或少有点像在黑暗中拍摄。是否有任何文件说明属性更新时会发生什么?

4

3 回答 3

19

你不能覆盖didSet,这不是一个正常的方法。实际上你没有覆盖didSet,你覆盖了属性本身。

didSet像观察者一样工作,仅仅因为您将自己的观察者设置在继承的属性上并不意味着任何其他观察者都会自动取消注册。因此,您的超类的观察者完全不受此影响,因此didSet最终将调用这两种方法。

现在,如果您在自己的didSet观察者中更改值,这不会导致递归,因为 Swift 运行时足够聪明,可以理解didSet更改自己观察到的属性的实现不会在这样做后再次被调用。运行时知道didSet它当前正在执行什么方法,并且如果变量在此方法返回之前发生更改,则不会再次执行该方法。此检查似乎不适用于超类。

所以*= 4导致超类观察者被调用,它设置*= 2并且导致子类观察者被再次调用,这将再次设置*= 4导致超类观察者被再次调用......等等。

通过显式使用super,你打破了这个循环,因为现在你没有设置你的覆盖属性,而是继承的超级属性,你并没有真正观察到那个超级属性,你只是在观察你自己的被覆盖的属性。

在某些语言中,您可能会在重写方法时遇到类似的问题,其中典型的解决方案也是super在其中一个调用中显式使用。

于 2016-07-06T13:08:20.637 回答
3

将 println() 放在两个 didSet 块中,您可以看到它首先重复调用超级实现,然后是 override,然后是 super,然后是 override……直到它爆炸。

我只能想象这是 Swift 中的一个错误。我在 Swift 1.2(与 Xcode 6.3 beta 捆绑)中遇到了同样的问题。


它绝对应该起作用,至少在我读到它的时候。来自https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID254

笔记

如果您在其自己的 didSet 观察者中为属性分配一个值,您分配的新值将替换刚刚设置的值。

在他们的 AudioChannel 示例之后(在注释下方引用):

笔记

在这两项检查的第一项中,didSet 观察者将 currentLevel 设置为不同的值。但是,这不会导致再次调用观察者。

struct AudioChannel {
    static let thresholdLevel = 10
    static var maxInputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                // cap the new audio level to the threshold level
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                // store this as the new overall maximum input level
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}
于 2015-03-02T21:05:33.430 回答
1

它似乎出于某种原因,尽管覆盖它仍然调用超类 didSet。

在第一个示例中,您最终会递归,因为设置 quad 会关闭超类 didSet,而后者又会关闭 quads did set 等。

在第二个示例中,设置该值会导致两个 didSet 各发生一次,然后四元组 didSet 也会在最后一次设置超级 didSet。

四元值 = 5

value =*2(superclass didSet) *4(subClass didSet) *2(superClass didSet) =80

于 2015-03-02T21:35:19.217 回答