为啥 Swift 闭包不捕获自我?
Posted
技术标签:
【中文标题】为啥 Swift 闭包不捕获自我?【英文标题】:Why Swift closure not capture self?为什么 Swift 闭包不捕获自我? 【发布时间】:2016-12-05 16:12:15 【问题描述】:我正在使用 Xcode Playground 测试快速关闭。
这是我的代码:
import UIKit
class A
var closure: ()->() =
var name: String = "A"
init()
self.closure =
self.name = self.name + " Plus"
deinit
print(name + " is deinit")
var a: A?
a = A()
a = nil
正如预期的那样,a 是由闭包自包含的,所以 a 永远不会被释放。
但是,当我在最后一行之前添加这一行时:
a?.closure = a?.name = "ttt"
然后,我在输出窗口中发现“A is deinit”,这意味着a被释放了。 为什么?是不回收参考吗?
为了测试,我使用了一个函数来设置闭包,代码是版本2:
import UIKit
class A
var closure: ()->() =
func funcToSetClosure()
self.closure = self.name = "BBB"
var name: String = "A"
init()
self.closure =
self.name = self.name + " Plus"
deinit
print(name + " is deinit")
var a: A?
a = A()
a?.funcToSetClosure()
a = nil
同样,a 永远不会被释放。
所以我得出结论,当闭包被init或类中的函数设置时,会引起循环引用,当它在类中设置时,不会引起循环引用。我说的对吗?
【问题讨论】:
【参考方案1】:在这两种情况下都有保留周期。区别在于 reference 的性质,而不是设置 closure
的 place。这种差异体现在打破循环需要什么:
self
。当您发布对a
的引用时,不足以打破循环,因为循环是直接自引用的。为了打破这个循环,您应该还在将a
设置为nil
之前将a.closure
设置为nil
,但你没有这样做。
在“外部”情况下,引用为a
。只要您的a
引用未设置为nil
,就会有一个保留周期。但您最终确实将其设置为nil
,这足以打破循环。
(插图来自 Xcode 的内存图功能。太酷了。)
【讨论】:
感谢大家的帮助,终于明白了,也感谢Xcode的内存图。【参考方案2】:正如SIL documentation 所说,当您在闭包中捕获局部变量时,它将通过引用计数存储在堆中:
捕获的局部变量和
indirect
值类型的有效载荷是 存储在堆上。@box T
类型是一个引用计数类型,它 引用一个包含T
类型的可变值的框。
所以当你说:
var a : A? = A()
a?.closure = a?.name = "ttt"
您确实有一个参考周期(您可以轻松验证)。这是因为A
的实例引用了closure
属性,该属性引用了堆分配的盒装A?
实例(由于它被闭包捕获),而后者又引用了A
的实例。
但是,你接着说:
a = nil
这将堆分配的装箱A?
实例的值设置为.none
,从而释放其对A
实例的引用,因此意味着您不再有引用循环,因此A
可以已解除分配。
只是让a
超出范围而不分配a = nil
将不会打破引用循环,因为A?
在堆上的实例仍由closure
保留A
的属性,A?
实例仍保留该属性。
【讨论】:
【参考方案3】:导致保留循环的原因是您在闭包中引用了self
。
var a: A?
a = A()
a?.closure = a?.name = "ttt"
a = nil
您将闭包更改为不再引用self
,这就是它被释放的原因。
在最后一个例子中,你让它在闭包中再次引用self
,这就是它不释放的原因。有很多方法可以解决这个问题,这篇文章是一个很好的列表,列出了何时在 swift 中使用每种情况:How to Correctly handle Weak Self in Swift Blocks with Arguments
我想你正在寻找这样的东西,你在块内使用对 self 的弱引用。 Swift 有一些新方法可以做到这一点,最常见的是在代码块前面使用 [unowned self]
表示法。
init()
self.closure = [unowned self] in
self.name = self.name + " Plus"
更多关于这里发生的事情的阅读:Shall we always use [unowned self] inside closure in Swift
【讨论】:
以上是关于为啥 Swift 闭包不捕获自我?的主要内容,如果未能解决你的问题,请参考以下文章