【问题标题】:Why does adding a closure capture list prevent my instance from being deallocated?为什么添加闭包捕获列表会阻止我的实例被释放?
【发布时间】:2017-04-15 03:42:59
【问题描述】:
class Name {
    var name: String
    init(name: String) {
        self.name = name
    }
    deinit {
        print("\(name) deinit")
    }
}
var x: Name? = Name(name: "abc")

var someClosure = {
    print("\(x?.name)")
}

someClosure()

x = nil

然后控制台会输出:

Optional("abc")
abc deinit

可以看出调用了“deinit”函数。所以不会形成强参考循环。 但是如果我在闭包中添加一个捕获列表:

var someClosure = { [x] in
    print("\(x?.name)")
}

控制台会输出:

Optional("abc")

并且没有调用“deinit”函数。所以对象和引用形成了一个强引用循环。

是什么原因?这两个条件有什么区别?

【问题讨论】:

    标签: swift closures reference-cycle


    【解决方案1】:

    首先,在这两种情况下都没有强保留循环 - 你只是有一个全局闭包变量,它持有对你的类实例的强引用,因此防止它被释放。

    在您的第一个示例中,当您在闭包中捕获 x 时:

    var someClosure = {
        print("\(x?.name)")
    }
    

    你得到的(实际上)是对引用的引用——也就是说,闭包对x 的存储有一个引用,然后它对你的类实例有一个引用。当您将x 设置为nil 时,闭包仍然引用x 的存储,但现在x 没有引用您的类实例。因此,您的类实例不再具有对其的任何强引用,并且可以被释放。

    在第二个示例中,当您使用 capture list:

    var someClosure = { [x] in
        print("\(x?.name)")
    }
    

    您正在复制 x 本身——也就是说,您正在复制对类实例的引用。因此,只要您的课程存在,关闭就会保留您的课程。将x 设置为nil 不会影响闭包对您的实例的引用,因为它拥有对它的强引用。

    【讨论】:

    • 我知道你的答案,我赞成。谢谢!
    【解决方案2】:

    这就是我认为这里发生的事情。

    ARC 在创建x 时将引用计数添加到x。当使用捕获列表调用闭包时,它会添加另一个计数(闭包捕获对象:从而通过增加引用计数向编译器表明它将来需要x)。所以x 现在的 ARC 计数为 2。

    当您将x 分配给nil 时,它会将引用计数减1。如果计数为0,或者只有对该对象的弱引用,则该对象将被取消初始化。如果计数大于 0,并且引用是强的,则保留该对象。

    如果没有明确地将捕获列表传递给闭包,它只会弱捕获对象。然后,ARC 决定一旦闭包完成,就可以安全地取消该对象。传递捕获列表向 ARC 表明可能需要该对象才能使闭包多次操作,因此进行了强引用。

    在x = nil 之后再次调用someClosure(),看看这两种情况会发生什么。

    【讨论】:

    • 我认为这非常接近。问题是编译器本身并没有释放闭包。如果您将代码放入 do { ... } 块中,则闭包和 x 将被释放..
    • 是的,看起来确实如此。
    • 这里没有弱引用 ;)
    猜你喜欢
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 2012-02-01
    • 1970-01-01
    • 2023-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多