Swift 闭包导致与 self 的强保留循环

Swift closures causing strong retain cycle with self

我只是想知道我是否理解正确。因此,根据苹果文档,当您将闭包创建为 class 实例的 属性 并且该闭包引用 self(The class that created the closure 属性) 这将导致强大的保留周期,最终 class 和闭包都不会被释放。所以用外行的话来说,这意味着如果我有一个 class 有一个 属性 并且 属性 是一个闭包,一旦我在 [=29= 中分配了那个闭包的功能] 声明将导致强保留循环的闭包 属性。这是我的意思的一个简单例子

class SomeViewController{
  let myClosure:()->Void

  public func someFunction(){
    ....bunch of code
    myClosure = {
       self.dismiss(blahBlahBlah)
    }
  }
}

这最终会导致保留循环,因为闭包保持对 self 的强引用,即创建闭包 属性 的 class。现在根据 apple 来解决这个问题,我会像这样定义一个捕获列表

class SomeViewController{
  let myClosure:()->Void

  public func someFunction(){
    ....bunch of code
    myClosure = { [weak self] in
       self?.dismiss(blahBlahBlah)
    }
  }
}

注意我是如何把 [weak self] 放在 in 语句之前的。这让闭包知道只持有对 self 的弱引用而不是强引用。 IM 应该在 self 可以活出闭包时使用 weak 或在闭包和 self 存活相同持续时间时使用 unowned。

我从这里 Automatic Reference Counting 和那个 link 的闭包强引用循环部分得到了这个信息,有这句话 "A strong reference cycle can also occur if you assign a closure to a property of a class instance, and the body of that closure captures the instance" 我大约 90% 确定我理解正确,但只有那 10% 的怀疑。那我说的对吗?

我问这个问题的原因是我在我的视图中为我的一些按钮使用了回调。这些回调会调用 self,但在那种情况下,self 是响应回调的视图控制器,而不是实际视图本身。这是我怀疑自己的地方,因为我从那句话中强调了我认为我不需要在所有这些按钮回调上放置 [weak self],但我只是确定一下。这是一个例子

class SomeViewController {
    let someSubview:UIView

    override viewDidLoad() {
       //Some Subview has a button and in that view I just have some action that gets fired off calling the callback here in the view controller I don't need to use the [weak self] in this scenario because closure property is not in this class correct?
       someSubview.someButtonsCallback = {
       ....run code then 
       self?.dismiss(blahBlahBlah)
     }
 }

是的,这仍然会导致保留周期。

最简单的保留循环是 2 个对象,每个对象都相互有强引用,但 3 向和更大的保留循环也是可能的。

在你的例子中,你有一个视图控制器,它的视图包含一个按钮(一个强引用)。该按钮对一个闭包有一个强引用。闭包使用 self 强烈引用视图控制器。所以视图拥有按钮。该按钮拥有闭包。闭包拥有视图控制器。如果您关闭视图控制器(假设它是模态的),那么它应该被释放。然而,因为你有这个 3-way retain cycle,它不会被释放。您应该使用 print 语句向视图控制器添加一个 deinit 方法并尝试一下。

解决方案是添加捕获列表([weak self] 位),就像您在第一个示例中所做的那样。

注意一个常见的模式是添加一个捕获列表,然后将弱变量映射到闭包内部的强变量:

let myClosure = { [weak self] in 
  guard let strongSelf = self else { return }
  //...
  strongSelf.doSomething()
}

这样,如果闭包仍然处于活动状态但拥有它的对象已被释放,则开头的 guard 语句会检测到 self 为 nil 并在闭包开始时退出。否则你每次引用它时都必须打开可选的。

在某些情况下,捕获列表中的对象(在这些示例中为 self)也可能在正在执行的闭包中间被释放,这可能会导致不可预测的行为。 (血淋淋的细节:这只是当闭包 运行 在与捕获列表中对象的所有者不同的线程上时,但是完成处理程序很常见 运行 在后台线程上,所以它确实发生)

想象一下:

let myClosure = { [weak self] in 

  self?.step1() //1

  //time-consuming code

  self?.property = newValue //2

  //more time-consuming code

  self?.doSomething() //3

  //even more time-consuming code

  self?.doSomethingElse() //4
}

对于上面的代码,如果闭包在后台线程上 运行,self 可能在第 1 步仍然有效,但是当您执行第 2 步时,self 已经被释放。第 3 步和第 4 步也是如此。通过在闭包的开头添加 guard strongSelf = self else { return },您可以在闭包的入口处进行测试,以确保 self 仍然有效,如果有效,则让闭包创建一个强引用仅在闭包到达 运行 时存在,并且防止 self 在闭包代码执行时被释放。)