据我所知,并在this线程中提到,如果我在其didSet
观察者中设置属性值,则它不应再次触发观察者。好的,然后我写了一段这样的代码:
class B {
var i = 0 {
didSet {
print("didSet called")
self.i += 1
}
}
}
var y = B()
y.i = 2
print(y.i)
此代码按预期输出"didSet called"
和3
作为输出。但是我对此代码做了一些小的更改,如下所示:
class B {
var i = 0 {
didSet {
print("didSet called")
doit(val: self)
}
}
func doit(val: B) {
val.i += 1
}
}
var y = B()
y.i = 2
print(y.i)
但是现在它陷入无限循环打印"didSet called"
。为什么如果我通过在函数参数中传递变量{strong>内didSet
来设置值,它又会触发didSet
吗?由于传递的对象应该引用同一对象,所以我不知道为什么会发生这种情况。我进行了测试,如果通过didSet
中的闭包而不是普通函数进行设置,它将再次进入无限循环。
更新: 有趣的是,这甚至触发了无限循环:
class B {
var i = 0 {
didSet {
print("called")
doit()
}
}
func doit() {
self.i += 1
}
}
答案 0 :(得分:4)
在检查了Swift github并询问了有关此问题的问题后,我发现这个问题看起来似乎更加复杂。但是有一个关于此问题的特定规则:
didSet
观察者仅在访问以下属性时才会触发 自己的didSet
观察者可以通过直接内存访问来完成。
问题在于,何时直接访问属性是有点模棱两可的(除非您是Swift的开发人员)。影响我的问题的一个重要功能是:
类实例方法从不直接访问类属性。
尽管我可以争辩说,只要您在didSet
观察中调用实例成员时,实例成员应该能够直接访问该属性,该引用就会显示我的代码存在问题。当我有这样的代码时:
class B {
var i = 0 {
didSet {
print("called")
doit()
}
}
func doit() {
self.i += 1
}
}
doit()
函数无法直接访问i
,这会再次触发didSet
导致无限循环。
现在有什么解决方法?
您可以使用inout
将属性从其自身的didSet
传递到实例函数,而无需触发didSet
。像这样:
class B {
var i = 0 {
didSet {
print("called")
doit(&i)
}
}
func doit(_ i: inout Int) {
i += 1
}
}
最后一件事。从 Swift 5 开始,为自己的didSet
中的属性选择直接内存访问的条件将受到更大的限制。基于github,仅将使用直接内存访问的条件如下:
Within a variable's own didSet/willSet specifier, access its storage directly if either: 1) It's a 'plain variable' (i.e a variable that's not a member). 2) It's an access to the member on the implicit 'self' declaration. If it's a member access on some other base, we want to call the setter as we might be accessing the member on a *different* instance.
这意味着类似以下的代码将触发无限循环,而现在还不会触发:
class B {
var i = 0 {
didSet {
print("called")
var s = self
s.i += 1
}
}
}