Swift:覆盖 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

如果我更新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

这或多或少就像在黑暗中射击。是否有任何关于属性更新时会发生什么的文件?

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

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

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

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

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

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

在两个 didSet 块中放置一个 println(),你可以看到它首先重复调用超级实现,然后是覆盖,然后是超级,然后是覆盖......直到它爆炸。

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


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

注意

如果将值分配给属性自己的 didSet 观察器中的属性,则分配的新值将替换刚刚设置的值。

他们的音频频道样本之后(在注释下方引用):

注意

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

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

它出于某种原因出现,尽管覆盖它仍然调用superClass didSet。

在第一个示例中,您最终会以递归结束,因为设置 quad 会触发超类 didSet,而超类 didSet 又会触发 quad 确实设置等。

在第二个示例中,该值会导致两个 didSet 分别出现一次,然后四 didSet 也会设置上次打开的超级 didSet。

四边形值 = 5

值 =*2(超类 didSet) *4(子类 didSet) *2(superClass didSet) =80

最新更新