Swift:覆盖didSet导致递归

时间:2015-01-21 12:57:12

标签: swift recursion 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

如果我用

更新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 个答案:

答案 0 :(得分:14)

您无法覆盖didSet,这不是常规方法。实际上你没有覆盖didSet,你覆盖了房产本身。

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

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

因此*= 4导致超类观察者被调用,它设置*= 2并导致再次调用子类观察者,这将再次设置*= 4导致超类观察者再次被召唤......等等。

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

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

答案 1 :(得分:3)

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

我只能想象这是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
            }
        }
    }
}

答案 2 :(得分:2)

问题是您不应该使用didSet来更改值,因为它会导致递归,而应该使用set:

var TwiceInt : Int {
    get {
        return TwiceInt
    }
    set (newValue) {
        TwiceInt *= 2
    }
}

答案 3 :(得分:1)

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

在第一个例子中,你最终会进行递归,因为设置了四个超级类的设置,然后设置了套装,这反过来又设置了四元组等等。

在第二个示例设置中,该值导致两个didSets各发生一次,然后quad didSet也会在上次设置super didSet。

quad.value = 5

value = * 2(超类didSet)* 4(subClass didSet)* 2(superClass didSet)= 80