简体   繁体   English

为什么Swift关闭不能捕获自我?

[英]Why Swift closure not capture self?

I was testing swift closure with Xcode playground. 我正在测试Xcode游乐场的快速关闭。

This is my code: 这是我的代码:

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

As what is expected, a is self contained by closure, so a is never released. 正如预期的那样,a是封闭自包含的,所以a永远不会释放。

But, when I add this line before the last line: 但是,当我在最后一行之前添加这一行时:

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

Then, I found "A is deinit" in the output window, which means a is released. 然后,我在输出窗口中发现“A is deinit”,这意味着a已被释放。 Why? 为什么? is a not recycle reference? 是不是回收参考?

To be test, I use a function to set the closure, which the code is version 2: 要进行测试,我使用一个函数来设置闭包,代码是版本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

Again, a is never released. 同样,a永远不会被释放。

So I got the conclusion, when closure is set by init or a function in the class, it will cause recycle reference, when it is set out side the class, it will not cause recycle reference. 所以我得出了结论,当闭包由init或类中的函数设置时,它将导致循环引用,当它被设置在类的侧面时,它不会导致循环引用。 Am I right? 我对吗?

There are retain cycles in both cases. 两种情况都有保留周期。 The difference is the nature of the reference , not the place where closure is set. 不同之处在于引用的性质,而不是设置closure位置 This difference is manifested in what it takes to break the cycle: 这种差异表现在打破周期所需的条件:

  • In the "inside" situation, the reference inside the closure is self . 在“内部”情况下,闭包内部的引用是self When you release your reference to a , that is insufficient to break the cycle, because the cycle is directly self-referential. 当你释放对a的引用时,这不足以打破循环,因为循环是直接自引用的。 To break the cycle, you would have had also to set a.closure to nil before setting a to nil , and you didn't do that. 要打破这个恶性循环,你将不得不设置a.closurenil之前设置anil ,而你没有这样做。

在此输入图像描述

  • In the "outside" situation, the reference is a . 在“外部”情况下,参考是a There is a retain cycle so long as your a reference is not set to nil . 只要您a引用未设置为nil有一个保留周期。 But you eventually do set it to nil , which is sufficient to break the cycle. 最终将其设置为nil ,这足以打破这种循环。

在此输入图像描述

(Illustrations come from Xcode's memory graph feature. So cool.) (插图来自Xcode的记忆图功能。太酷了。)

What causes the retain cycle is that you reference self in the closure. 导致保留周期的原因是您在闭包中引用self

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

You change the closure to no longer reference self , that's why it is deallocated. 您将闭包更改为不再引用self ,这就是它被解除分配的原因。

In the final example, you make it reference self again in the closure, that is why it does not deallocate. 在最后一个例子中,你让它在闭包中再次引用self ,这就是为什么它不会释放。 There are ways around this, this post is a great list of when to use each case in swift: How to Correctly handle Weak Self in Swift Blocks with Arguments 有这方面的方法,这篇文章是一个伟大的列表,在swift中何时使用每个案例: 如何正确处理带有参数的Swift块中的弱自我

I would imagine you are looking for something like this, where you use a weak reference to self inside the block. 我想你正在寻找这样的东西,你在块内使用弱引用。 Swift has some new ways to do this, most commonly using the [unowned self] notation at the front of the block. Swift有一些新方法可以做到这一点,最常见的是使用块前面的[unowned self]符号。

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

More reading on what is going on here: Shall we always use [unowned self] inside closure in Swift 更多关于这里发生的事情的阅读: 我们总是在Swift中使用[unowned self]内部封闭

As the SIL documentation says, when you capture a local variable in a closure, it will be stored on the heap with reference counting: 正如SIL文档所说,当您在闭包中捕获局部变量时,它将通过引用计数存储在堆上:

Captured local variables and the payloads of indirect value types are stored on the heap. 捕获的局部变量和indirect值类型的有效负载存储在堆上。 The type @box T is a reference-counted type that references a box containing a mutable value of type T . 类型@box T是引用计数类型,它引用包含类型T的可变值的框。

Therefore when you say: 因此当你说:

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

you do have a reference cycle (which you can easily verify). 有一个参考周期(你可以轻松验证)。 This is because the instance of A has a reference to the closure property, which has a reference to the heap-allocated boxed A? 这是因为A的实例有一个对closure属性的引用,它引用了堆分配的盒装A? instance (due to the fact that it's being captured by the closure), which in turn has a reference to the instance of A . 实例(由于它被闭包捕获的事实),而后者又引用了A的实例。

However, you then say: 但是,你说:

a = nil

Which sets the heap-allocated boxed A? 哪个设置堆分配的盒装A? instance's value to .none , thus releasing its reference to the instance of A , therefore meaning that you no longer have a reference cycle, and thus A can be deallocated. 实例的值为.none ,因此释放其对A实例的引用,因此意味着您不再具有引用循环,因此可以取消分配A

Just letting a fall out of scope without assigning a = nil will not break the reference cycle, as the instance of A? 仅仅让a掉下来的范围不分配a = nil 不会打破循环引用,作为实例A? on the heap is still being retained by the closure property of A , which is still being retained by the A? 在堆上仍然被Aclosure属性保留,它仍被A?保留A? instance. 实例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM