我对闭包的理解是,它会强捕获所有直接引用的对象,无论对象变量是否在闭包之外声明
weak
或strong
,如果我们想要弱地捕获它们,那么我们需要显式地定义一个捕获列表并在该捕获列表中将它们标记为 weak
。
obj?.callback = { [weak obj] in
obj?.perform()
}
但是在我的测试中,我发现如果变量已经
weak
在闭包之外,那么我们就不需要使用捕获列表来弱捕获它了。
class Foo {
var callback: (() -> ())?
init() {
weak var weakSelf = self
callback = {
weakSelf?.perform()
}
// is above equivalent to below in terms of memory management?
// callback = { [weak self] in
// self?.perform()
// }
}
func perform() {
print("perform")
}
deinit {
print("deinit")
}
}
let foo = Foo() // prints "deinit foo"
上面的代码片段没有创建任何保留周期。这是否意味着如果变量已经声明了,我们不需要在捕获列表中显式捕获弱对象变量
weak
并且捕获列表只是提供了比在闭包内使用它们之前创建weak
变量的语法优势。
在这个具体示例中,但您需要非常小心地思考正在发生的事情。
首先,是的,这是相同的。我们可以通过生成 SIL (
swiftc -emit-sil main.swift
) 来判断。除了 self
与 weakSelf
的名称不同之外,它们生成完全相同的未优化 SIL。为了使它更清楚,我将更改捕获列表中变量的名称(这只是重命名,它不会改变行为):
weak var weakSelf = self
callback = {
weakSelf?.perform()
}
callback = { [weak weakSelf = self] in
weakSelf?.perform()
}
$ swiftc -emit-sil weakSelf.swift > weakSelf.sil
$ swiftc -emit-sil weak_self.swift > weak_self.sil
$ diff -c weakSelf.sil weak_self.sil
*** weakSelf.sil 2022-03-27 10:58:13.000000000 -0400
--- weak_self.sil 2022-03-27 11:01:22.000000000 -0400
***************
*** 102,108 ****
// Foo.init()
sil hidden @$s4main3FooCACycfc : $@convention(method) (@owned Foo) -> @owned Foo {
! // %0 "self" // users: %15, %8, %7, %2, %22, %1
bb0(%0 : $Foo):
debug_value %0 : $Foo, let, name "self", argno 1, implicit // id: %1
%2 = ref_element_addr %0 : $Foo, #Foo.callback // user: %4
--- 102,108 ----
// Foo.init()
sil hidden @$s4main3FooCACycfc : $@convention(method) (@owned Foo) -> @owned Foo {
! // %0 "self" // users: %8, %7, %15, %2, %22, %1
bb0(%0 : $Foo):
debug_value %0 : $Foo, let, name "self", argno 1, implicit // id: %1
%2 = ref_element_addr %0 : $Foo, #Foo.callback // user: %4
除了用户评论
self
的顺序外,其他都是一样的。
但是在使用这些知识时要非常非常小心。这恰好是真的,因为在其他地方存在对
self
的强烈引用。在这条路上走得太远可能会导致混乱。例如,考虑以下两种方法:
// Version 1
weak var weakObject = Object()
let callback = {
weakObject?.run()
}
callback()
// Version 2
var weakObject = Object()
let callback = { [weak weakObject] in
weakObject?.run()
}
callback()
这些行为根本不一样。在第一个版本中,创建
weakObject
时 callback
已经发布,因此 callback()
不执行任何操作。编译器将生成有关此问题的警告,因此在大多数情况下,这不太可能创建,但作为规则,您通常应该在捕获列表中进行弱捕获,以便它尽可能接近闭包创建发生,并赢得胜利不要意外地被意外释放。