为什么添加闭包捕获列表会阻止我的实例被释放?
Why does adding a closure capture list prevent my instance from being deallocated?
class Name {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) deinit")
}
}
var x: Name? = Name(name: "abc")
var someClosure = {
print("\(x?.name)")
}
someClosure()
x = nil
然后控制台会输出:
Optional("abc")
abc deinit
可以看出调用了"deinit"函数。所以没有形成强引用循环。
但是如果我在闭包中添加一个捕获列表:
var someClosure = { [x] in
print("\(x?.name)")
}
控制台会输出:
Optional("abc")
并且"deinit"函数没有被调用。所以对象和引用形成了一个强引用循环。
这是什么原因?这两个条件有什么区别?
这就是我认为这里发生的情况。
ARC 在创建 x
时将引用计数添加到 x
。当使用捕获列表调用闭包时,它会添加另一个计数(闭包 captures 对象:从而向编译器表明它将来需要 x
,通过增加引用计数)。所以 x
现在 ARC 计数为 2。
当您将 x
分配给 nil
时,它会将引用计数减 1。如果计数为 0,或者只有对对象的弱引用,则对象将被取消初始化。如果计数大于 0,并且引用很强,则保留对象。
如果不显式地将捕获列表传递给闭包,它只会微弱地捕获对象。然后 ARC 决定一旦闭包完成就可以安全地 deinit 对象。传递捕获列表向 ARC 指示闭包可能需要对象在不同的多次操作,因此进行了强引用。
在 x = nil
之后再次调用 someClosure()
,看看两种情况下会发生什么。
首先,在这两种情况下都没有强保留 cycle——你只是简单地拥有一个全局闭包变量,它持有对你的 class 实例的强引用,因此防止它被释放。
在您的第一个示例中,当您在闭包中捕获 x
时:
var someClosure = {
print("\(x?.name)")
}
你得到的(实际上)是对引用的引用——也就是说,闭包引用了 x
的存储,然后它引用了你的 class 实例。当您将 x
设置为 nil
时 – 闭包仍然引用 x
的存储,但现在 x
没有 引用了您的 class 实例。因此,您的 class 实例不再有任何强引用,可以被释放。
在你使用 capture list 的第二个例子中:
var someClosure = { [x] in
print("\(x?.name)")
}
您正在复制 x
本身——也就是说,您正在复制对class 实例的引用。因此,闭包将保留你的 class 只要它存在。将 x
设置为 nil
不会影响闭包对您实例的引用,因为它拥有 自己的 强引用。
class Name {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) deinit")
}
}
var x: Name? = Name(name: "abc")
var someClosure = {
print("\(x?.name)")
}
someClosure()
x = nil
然后控制台会输出:
Optional("abc")
abc deinit
可以看出调用了"deinit"函数。所以没有形成强引用循环。 但是如果我在闭包中添加一个捕获列表:
var someClosure = { [x] in
print("\(x?.name)")
}
控制台会输出:
Optional("abc")
并且"deinit"函数没有被调用。所以对象和引用形成了一个强引用循环。
这是什么原因?这两个条件有什么区别?
这就是我认为这里发生的情况。
ARC 在创建 x
时将引用计数添加到 x
。当使用捕获列表调用闭包时,它会添加另一个计数(闭包 captures 对象:从而向编译器表明它将来需要 x
,通过增加引用计数)。所以 x
现在 ARC 计数为 2。
当您将 x
分配给 nil
时,它会将引用计数减 1。如果计数为 0,或者只有对对象的弱引用,则对象将被取消初始化。如果计数大于 0,并且引用很强,则保留对象。
如果不显式地将捕获列表传递给闭包,它只会微弱地捕获对象。然后 ARC 决定一旦闭包完成就可以安全地 deinit 对象。传递捕获列表向 ARC 指示闭包可能需要对象在不同的多次操作,因此进行了强引用。
在 x = nil
之后再次调用 someClosure()
,看看两种情况下会发生什么。
首先,在这两种情况下都没有强保留 cycle——你只是简单地拥有一个全局闭包变量,它持有对你的 class 实例的强引用,因此防止它被释放。
在您的第一个示例中,当您在闭包中捕获 x
时:
var someClosure = {
print("\(x?.name)")
}
你得到的(实际上)是对引用的引用——也就是说,闭包引用了 x
的存储,然后它引用了你的 class 实例。当您将 x
设置为 nil
时 – 闭包仍然引用 x
的存储,但现在 x
没有 引用了您的 class 实例。因此,您的 class 实例不再有任何强引用,可以被释放。
在你使用 capture list 的第二个例子中:
var someClosure = { [x] in
print("\(x?.name)")
}
您正在复制 x
本身——也就是说,您正在复制对class 实例的引用。因此,闭包将保留你的 class 只要它存在。将 x
设置为 nil
不会影响闭包对您实例的引用,因为它拥有 自己的 强引用。