为什么 Swift 闭包不捕获 self?
Why Swift closure not capture self?
我正在用 Xcode 游乐场测试 swift 关闭。
这是我的代码:
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" }
然后,我在输出window中发现"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或者class中的函数设置时,它会导致回收引用,当它被设置在class之外时,它不会导致回收参考。我说得对吗?
导致循环保留的原因是您在闭包中引用了self
。
var a: A?
a = A()
a?.closure = { a?.name = "ttt" }
a = nil
您将闭包更改为不再引用 self
,这就是它被释放的原因。
在最后一个示例中,您在闭包中再次引用 self
,这就是它不释放的原因。有很多方法可以解决这个问题,这个 post 是一个很好的列表,列出了何时使用 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
两种情况下都有循环保留。不同之处在于 reference 的性质,而不是设置 closure
的 place 的性质。这种差异体现在打破循环所需要的东西:
- 在"inside"的情况下,闭包内部的引用是
self
。当你释放你对a
的引用时,即不足以打破循环,因为循环是直接自引用的。要打破这个循环,在将 a
设置为 nil
之前,您需要 also 将 a.closure
设置为 nil
,而您没有别那样做。
- 在"outside"的情况下,引用是
a
。只要您的 a
引用未设置为 nil
,就会有一个保留周期。但你最终做将其设置为nil
,足以打破循环。
(插图来自 Xcode 的内存图功能。太酷了。)
正如SIL documentation所说,当你在闭包中捕获一个局部变量时,它会被存储在堆上并进行引用计数:
Captured local variables and the payloads of indirect
value types are
stored on the heap. The type @box T
is a reference-counted type that
references a box containing a mutable value of type T
.
因此当你说:
var a : A? = A()
a?.closure = { a?.name = "ttt" }
你做有一个参考周期(你可以很容易地验证)。这是因为 A
的实例引用了 closure
属性,它引用了堆分配的盒装 A?
实例(因为它是被闭包捕获),它又引用了 A
.
的实例
然而,你接着说:
a = nil
它将堆分配的盒装 A?
实例的值设置为 .none
,从而释放其对 A
实例的引用,因此意味着您不再有引用循环,因此 A
可以被释放。
只是让 a
超出范围而不分配 a = nil
将 不会 打破引用循环,因为 A?
的实例堆仍由 A
的 closure
属性 保留,A?
实例仍保留。
我正在用 Xcode 游乐场测试 swift 关闭。
这是我的代码:
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" }
然后,我在输出window中发现"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或者class中的函数设置时,它会导致回收引用,当它被设置在class之外时,它不会导致回收参考。我说得对吗?
导致循环保留的原因是您在闭包中引用了self
。
var a: A?
a = A()
a?.closure = { a?.name = "ttt" }
a = nil
您将闭包更改为不再引用 self
,这就是它被释放的原因。
在最后一个示例中,您在闭包中再次引用 self
,这就是它不释放的原因。有很多方法可以解决这个问题,这个 post 是一个很好的列表,列出了何时使用 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
两种情况下都有循环保留。不同之处在于 reference 的性质,而不是设置 closure
的 place 的性质。这种差异体现在打破循环所需要的东西:
- 在"inside"的情况下,闭包内部的引用是
self
。当你释放你对a
的引用时,即不足以打破循环,因为循环是直接自引用的。要打破这个循环,在将a
设置为nil
之前,您需要 also 将a.closure
设置为nil
,而您没有别那样做。
- 在"outside"的情况下,引用是
a
。只要您的a
引用未设置为nil
,就会有一个保留周期。但你最终做将其设置为nil
,足以打破循环。
(插图来自 Xcode 的内存图功能。太酷了。)
正如SIL documentation所说,当你在闭包中捕获一个局部变量时,它会被存储在堆上并进行引用计数:
Captured local variables and the payloads of
indirect
value types are stored on the heap. The type@box T
is a reference-counted type that references a box containing a mutable value of typeT
.
因此当你说:
var a : A? = A()
a?.closure = { a?.name = "ttt" }
你做有一个参考周期(你可以很容易地验证)。这是因为 A
的实例引用了 closure
属性,它引用了堆分配的盒装 A?
实例(因为它是被闭包捕获),它又引用了 A
.
然而,你接着说:
a = nil
它将堆分配的盒装 A?
实例的值设置为 .none
,从而释放其对 A
实例的引用,因此意味着您不再有引用循环,因此 A
可以被释放。
只是让 a
超出范围而不分配 a = nil
将 不会 打破引用循环,因为 A?
的实例堆仍由 A
的 closure
属性 保留,A?
实例仍保留。