我们是否需要在 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) 来判断。除了 selfweakSelf 的名称不同外,它们生成完全相同的未优化 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() 什么都不做。编译器会生成一个警告,所以在大多数情况下,这是一个不太可能产生的错误,但通常你应该在捕获列表中进行弱捕获,以便它尽可能接近闭包创建,并赢得'不小心被意外释放了。