Swift: переопределение hasSet приводит к рекурсии
Когда переопределение наблюдателя 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
Это более или менее похоже на стрельбу в темноте. Есть ли какой-либо документ о том, что происходит при обновлении свойства?
Ответы
Ответ 1
Вы не можете переопределить didSet
, это не обычный метод. На самом деле вы не переопределили didSet
, вы переопределили свойство.
didSet
работает, как работает наблюдатели, и только потому, что вы устанавливаете свой собственный наблюдатель в унаследованном имуществе, не означает, что любой другой наблюдатель автоматически незарегистрирован. Таким образом, наблюдатель вашего суперкласса полностью не затронут этим, и поэтому в конце будут вызваны методы didSet
.
Теперь, если вы измените значение в своем собственном наблюдателе didSet
, это не вызовет рекурсии, так как среда выполнения Swift достаточно умна, чтобы понять, что реализация didSet
, изменяющая свое собственное наблюдаемое свойство, не будет называться снова после этого. Время выполнения знает, какой метод didSet
он выполняет в настоящий момент, и не будет выполнять этот метод еще раз, если переменная изменится до того, как этот метод вернется. Эта проверка, похоже, не работает через суперклассы.
Таким образом, *= 4
вызывает вызов суперкласса, который устанавливает *= 2
и вызывает повторный вызов наблюдателя подкласса, который снова установит *= 4
, чтобы вызывающий суперкласс-наблюдатель был вызван снова... и т.д.
Явным образом использую super
, вы нарушаете этот цикл, так как теперь вы не устанавливаете переопределенное свойство, а унаследованное свойство super, и вы не наблюдаете это свойство super, вы наблюдаете только свой собственный переопределенный.
Вы можете столкнуться с аналогичной проблемой с переопределенными методами на некоторых языках, где типичное решение также должно явно использовать super
при одном из вызовов.
Ответ 2
Поместите println() в оба блока doSet, вы увидите, что он многократно вызывает супер-реализацию сначала, затем переопределение, затем супер, затем переопределяет... до тех пор, пока он не взорвется.
Я могу только понять, что это ошибка в 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:
Примечание
Если вы присвоите значение свойству в своем собственном наблюдателе bySet, новое назначенное вами значение заменит тот, который был только что установлен.
и после их образца 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
}
}
}
}
Ответ 3
Проблема заключается в том, что вы не должны использовать didSet для изменения значения, потому что это вызовет рекурсию, вместо этого вы должны использовать set:
var TwiceInt : Int {
get {
return TwiceInt
}
set (newValue) {
TwiceInt *= 2
}
}
Ответ 4
он появляется по какой-то причине, несмотря на переопределение, он все еще вызывает суперкласс didSet.
В первом примере вы попадаете в рекурсию, потому что установка quad отменяет суперкласс classSet, который, в свою очередь, устанавливает квадранты, установил etc и т.д.
Во втором примере установки значения приводят к тому, что оба didSets появляются один раз, затем quad didSet также устанавливает суперданные в последний раз.
quad.value = 5
value = * 2 (superclass didSet) * 4 (subClass didSet) * 2 (superClass didSet) = 80