Swift 闭包与 self 之间会产生强引用循环问题

14

我只是想知道我是否正确理解了这个问题。因此,根据苹果文档,当您将闭包创建为类实例的属性,并且该闭包引用self(创建闭包属性的类),这将导致强烈的保留循环,最终类和闭包都将永远不会被释放。因此,简单来说,如果我有一个具有属性的类,该属性是一个闭包,并且一旦我分配了声明闭包属性的类中的该闭包的功能,那么这将导致强大的保留循环。以下是我所指的快速示例

class SomeViewController{
  let myClosure:()->Void

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

这最终会导致保留循环,因为闭包对创建闭包属性的类self保持强引用。现在根据苹果的建议,我将定义一个捕获列表,如下所示

class SomeViewController{
  let myClosure:()->Void

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

注意我在语句中把[weak self]放在in之前。这让闭包知道只保留对self的弱引用而不是强引用。当self可能比闭包存在时间更长或者self和闭包的生命周期相同时,应该使用weak或unowned。我从Automatic Reference Counting中获取了这些信息,在该链接的Strong Reference Cycles for Closures部分中有这样一句话:“如果将闭包分配给类实例的属性,并且该闭包的主体捕获了该实例,则也可能会发生强引用循环。”我大约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)
     }
 }
1个回答

15

是的,这仍然可能导致保留循环。

最简单的保留循环是两个对象彼此具有强引用,但也可能存在三路及更大规模的保留循环。

在您的情况下,您有一个视图控制器,其视图包含一个按钮(强引用)。按钮具有对闭包的强引用。闭包使用self强烈引用视图控制器。因此,视图拥有按钮。按钮拥有闭包。闭包拥有视图控制器。如果您关闭视图控制器(例如它是一个模态窗口),那么它应该被释放。但是,由于存在这个三路保留循环,它将不会被释放。您应该为您的视图控制器添加一个带有打印语句的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仍然有效,但当执行第二步时,self已被释放。第三步和第四步也是如此。通过在闭包开头添加guard strongSelf = self else { return },您可以在进入闭包时测试self是否仍然有效,如果有效,则使闭包创建一个只在闭包运行期间存在的强引用,防止在闭包代码执行时释放self


老实说,我还记得你在iPhoneDEvSdk.com上,当涉及到iOS信息时,你就像是神一样。我甚至不需要读完整个内容就知道你的答案是正确的,哈哈。谢谢你为我澄清这个问题,我现在要继续阅读剩下的内容了。 - Esko918
我需要去每一个闭包中,使用按钮按下的回调函数并添加 [weak self]。然后,如果在闭包运行之前self可能被释放,我还需要设置一个强引用变量。我的另一个问题是,在从self运行函数时,什么情况下我们不需要设置捕获列表?例如,在HTTP调用中。由于闭包由调用HTTP方法的类拥有,并且该方法仅存在于调用它的函数的范围内。我不需要使用 weak self,对吗? - Esko918

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接