Swift:覆盖didSet导致递归

chu*_*guy 13 recursion swift swift-playground didset

覆盖属性的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
Run Code Online (Sandbox Code Playgroud)

如果我更新QuadInt

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

q.value = 5 // q.value = 80
Run Code Online (Sandbox Code Playgroud)

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

value = 5
QuadInt:didSet ( value *= 4 )
value = 20
TwiceInt:didSet ( value *= 2 )
value = 40
TwiceInt:didSet ( value *= 2 )
value = 80
Run Code Online (Sandbox Code Playgroud)

这或多或少像在黑暗中拍摄.有关房产更新时会发生什么的文件吗?

Mec*_*cki 17

你不能覆盖didSet,这不是一个普通的方法.实际上你没有覆盖didSet,你覆盖了财产本身.

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

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

因此*= 4调用超类观察者的原因是,它设置*= 2并导致再次调用子类观察者,这将再次设置*= 4导致再次调用超类观察者......依此类推.

通过显式使用super,您可以打破该循环,因为现在您没有设置被覆盖的属性,但是继承的超级属性并没有真正观察到超级属性,您只是观察自己被覆盖的属性.

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


小智 3

将 println() 放在两个 didSet 块中,您可以看到它首先重复调用 super 实现,然后是 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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)