我们是否需要在 swift 闭包中为弱变量显式使用捕获列表?
Do we need to explicitly use capture list for weak variables in swift closure?
我对闭包的理解是,无论对象变量是在闭包外声明 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。为了更清楚,我将更改捕获列表中变量的名称(这只是重命名,不会改变行为):
weakSelf
weak var weakSelf = self
callback = {
weakSelf?.perform()
}
weak_self
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()
什么都不做。编译器会生成一个警告,所以在大多数情况下,这是一个不太可能产生的错误,但通常你应该在捕获列表中进行弱捕获,以便它尽可能接近闭包创建,并赢得'不小心被意外释放了。
我对闭包的理解是,无论对象变量是在闭包外声明 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。为了更清楚,我将更改捕获列表中变量的名称(这只是重命名,不会改变行为):
weakSelf
weak var weakSelf = self
callback = {
weakSelf?.perform()
}
weak_self
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()
什么都不做。编译器会生成一个警告,所以在大多数情况下,这是一个不太可能产生的错误,但通常你应该在捕获列表中进行弱捕获,以便它尽可能接近闭包创建,并赢得'不小心被意外释放了。