【问题标题】:Why Swift closure not capture self?为什么 Swift 闭包不捕获自我?
【发布时间】:2016-12-05 16:12:15
【问题描述】:

我正在使用 Xcode Playground 测试快速关闭。

这是我的代码:

import UIKit

class A{
    var closure: ()->() = {}

    var name: String = "A"
    init() {
        self.closure = {
            self.name = self.name + " Plus"
        }
    }

    deinit {
        print(name + " is deinit")
    }
}

var a: A?
a = A()
a = nil

正如预期的那样,a 是由闭包自包含的,所以 a 永远不会被释放。

但是,当我在最后一行之前添加这一行时:

a?.closure = { a?.name = "ttt" }

然后,我在输出窗口中发现“A is deinit”,这意味着a被释放了。 为什么?是不回收参考吗?

为了测试,我使用了一个函数来设置闭包,代码是版本2:

import UIKit

class A{
    var closure: ()->() = {}
    func funcToSetClosure(){
        self.closure = { self.name = "BBB"}
    }
    var name: String = "A"
    init() {
        self.closure = {
            self.name = self.name + " Plus"
        }
    }

    deinit {
        print(name + " is deinit")
    }
}



var a: A?


a = A()


a?.funcToSetClosure()


a = nil

同样,a 永远不会被释放。

所以我得出结论,当闭包被init或类中的函数设置时,会引起循环引用,当它在类中设置时,不会引起循环引用。我说的对吗?

【问题讨论】:

    标签: swift closures automatic-ref-counting


    【解决方案1】:

    在这两种情况下都有保留周期。区别在于 reference 的性质,而不是设置 closureplace。这种差异体现在打破循环需要什么:

    • 在“内部”情况下,闭包内的引用是self。当您发布对a 的引用时,不足以打破循环,因为循环是直接自引用的。为了打破这个循环,您应该在将a 设置为nil 之前将a.closure 设置为nil,但你没有这样做。

    • 在“外部”情况下,引用为a。只要您的a 引用未设置为nil,就会有一个保留周期。但您最终确实将其设置为nil,这足以打破循环。

    (插图来自 Xcode 的内存图功能。太酷了。)

    【讨论】:

    • 感谢大家的帮助,终于明白了,也感谢Xcode的内存图。
    【解决方案2】:

    正如SIL documentation 所说,当您在闭包中捕获局部变量时,它将通过引用计数存储在堆中:

    捕获的局部变量和indirect值类型的有效载荷是 存储在堆上。 @box T 类型是一个引用计数类型,它 引用一个包含 T 类型的可变值的框。

    所以当你说:

    var a : A? = A()
    a?.closure = { a?.name = "ttt" }
    

    确实有一个参考周期(您可以轻松验证)。这是因为A 的实例引用了closure 属性,该属性引用了堆分配的盒装A? 实例(由于它被闭包捕获),而后者又引用了A 的实例。

    但是,你接着说:

    a = nil
    

    这将堆分配的装箱A?实例的值设置为.none,从而释放其对A实例的引用,因此意味着您不再有引用循环,因此A可以已解除分配。

    只是让a 超出范围而不分配a = nil不会打破引用循环,因为A? 在堆上的实例仍由closure 保留A 的属性,A? 实例仍保留该属性。

    【讨论】:

      【解决方案3】:

      导致保留循环的原因是您在闭包中引用了self

      var a: A?
      a = A()
      a?.closure = { a?.name = "ttt" }
      a = nil
      

      您将闭包更改为不再引用self,这就是它被释放的原因。

      在最后一个例子中,你让它在闭包中再次引用self,这就是它不释放的原因。有很多方法可以解决这个问题,这篇文章是一个很好的列表,列出了何时在 swift 中使用每种情况:How to Correctly handle Weak Self in Swift Blocks with Arguments

      我想你正在寻找这样的东西,你在块内使用对 self 的弱引用。 Swift 有一些新方法可以做到这一点,最常见的是在代码块前面使用 [unowned self] 表示法。

      init() {
          self.closure = { [unowned self] in
              self.name = self.name + " Plus"
          }
      }
      

      更多关于这里发生的事情的阅读:Shall we always use [unowned self] inside closure in Swift

      【讨论】:

        猜你喜欢
        • 2021-01-20
        • 2016-08-05
        • 2012-11-17
        • 2021-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-04
        相关资源
        最近更新 更多