通过函数进行内部设置时,didSet如何再次调用?

时间:2019-01-03 19:16:41

标签: swift

据我所知,并在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
    }
}

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