Swift闭包是否保留捕获的变量



我发现Swift闭包没有像我预期的那样保留捕获的变量。

class AAA {
}
var a1  =   AAA() as AAA?                  // expects RC == 1
var a2  =   { ()->AAA? in return a1 }      // expects RC == 2, retained by `Optional<AAA>`
a1      =   nil                            // expects RC == 1
a2()                                       // prints nil, ????

我对此感到非常困惑,因为我一直认为捕获的变量将默认保留。但是,若我使用捕获列表显式地捕获它,它将被保留。

class AAA {
}
var a1  =   AAA() as AAA?
var a2  =   { [a1]()->AAA? in return a1 }
a1      =   nil
a2() // prints {AAA}, alive as expected.

我重新阅读了Swift的手册,但找不到相关的描述。捕获列表用于显式设置unowned,我仍然感到困惑。什么是正确的行为,为什么会发生这种情况?

是,记录在捕获值:中

Swift确定哪些应该通过引用捕获,哪些应该通过值复制。您不需要对amount或runningTotal进行注释,就可以说明它们可以在嵌套的增量函数中使用。Swift还处理在increment函数不再需要runningTotal时处理它所涉及的所有内存管理。

规则是:如果您引用一个捕获的变量而不修改它,它将按值捕获。如果您修改它,它将通过引用捕获。当然,除非您通过定义捕获列表来明确覆盖它。

附录上述声明似乎不正确。无论是否在闭包内进行了修改,捕获都是通过引用进行的。阅读@newacct评论。

您的示例在@newacct中没有意义。变量x实际上是在块外修改的。Swift足够聪明,可以发现您是在闭包内部还是外部修改变量。

正如文件所说:

作为一种优化,Swift可以捕获并存储一个值的副本,如果该值没有在闭包中或闭包之外发生突变。

关于@Eonil发布的问题,在第一个片段中,您在闭包中强烈引用了Optional<AAA>。但是,当您将a1设置为nil时,实际要做的是删除包装在可选项中的AAA类型的值。

当您调用闭包a2时,您会返回相同的可选项,其中没有值,这正是nil的意思。因此,持有强引用意味着您的闭包与a1共享相同的可选值。这并不意味着这个可选项不能设置为零。

在第二个代码片段中,您在捕获列表中捕获了a1。这意味着您复制a1,并且闭包内的值a1与闭包外的值a1无关。因此,即使您将a1设置为零,也不会影响您从闭包中得到的结果。

我的英语不好,我希望我能清楚地表达我的观点。或者你可以阅读这篇文章,我相信它会对你有很大帮助。

我在苹果开发者论坛上发布了同样的问题,并进行了讨论。虽然人们并不经常谈论引用计数,但我有一些想法。以下是我的结论:

  • 当一个值绑定到一个新的(显式)名称时,它总是被复制的。不管是var还是let
  • 引用类型中的复制表示RC+1。因为它复制了像C++shared_ptr<T>这样的强指针
  • 关闭(闭包捕获)不会更改任何内容,因为没有新名称。这和你们在同一个堆栈中使用它们是一样的。这就是引用的含义。没有与RC相关的内容,也没有更改RC,因为它有点像C++中的引用
  • 捕获列表是一个显式(let)名称。因此,它会导致复制,并生成RC+1
  • 当函数返回闭包时,它可能绑定到一个名称。如果是,RC+1,因为新绑定到一个名称
  • RC-1当一个值(因此是一个强指针)从其名称中解除绑定时
  • self的隐式引用是进行隐式名称绑定的唯一例外

在不违反这些规则的情况下,还有许多优化,无论如何,这些只是实现细节。

最新更新