为什么 onChange(of:perform:) 的参数被捕获?

Geo*_*e_E 0 automatic-ref-counting swift swiftui

我正在使用onChange(of:perform:)SwiftUI 修饰符。然后我想获得旧值,将其与新值进行比较。我阅读了文档,其中说:

闭包可以捕获先前的值以将其与新值进行比较。

举个例子:

.onChange(of: playState) { [playState] newState in
    model.playStateDidChange(from: playState, to: newState)
}
Run Code Online (Sandbox Code Playgroud)

我的问题是为什么在示例中被playState捕获在[ ]? 该playState值无需传入即可轻松访问。此外,这不是类的一部分,因此我认为无法通过捕获self某种类型来创建强引用。

为什么这个例子是这样写的?

New*_*Dev 5

在“正常”情况下,在不可变值类型(例如 a struct)中定义的闭包捕获它的值,它不会改变,所以一切都很好。

struct Foo {
  var a = "original"
  
  func makeFn() -> () -> Void {
     return { print(a) }
  }
}

var foo = Foo()
let fn = foo.makeFn()
foo.a = "changed"

fn() // "original"
Run Code Online (Sandbox Code Playgroud)

但是对于@State,实际值存储在 SwiftUI 维护的某个全局存储中,因此它的行为基本上就像具有引用语义一样。

当闭包被调用时,状态值已经改变了,所以print(a)按照上面的方法通过@State属性包装器访问一个值,它检索然后更新的值。

为了解决这个问题,您可以使用捕获列表将该属性捕获到闭包的局部变量中:

return { [a] in print(a) }
Run Code Online (Sandbox Code Playgroud)

这当然是一个简化的例子,SwiftUI 可能在幕后做其他事情,但我认为它传达了这一点。


要查看 SwiftUI 中的差异,请尝试以下操作:

.onChange(of: playState) { [playState] newState in
    print(playState, self.playState, newState)
}
Run Code Online (Sandbox Code Playgroud)

输出将是这样的:

original new new
Run Code Online (Sandbox Code Playgroud)

playState是在定义闭包时(即body计算时)捕获的局部变量,self.playState通过 访问该值@State,该值已更改,并且newState显然是具有新值的传入参数。