Why Swift closure not capture self?

本秂侑毒 提交于 2019-11-29 10:37:53

There are retain cycles in both cases. The difference is the nature of the reference, not the place where closure is set. This difference is manifested in what it takes to break the cycle:

  • In the "inside" situation, the reference inside the closure is self. When you release your reference to a, that is insufficient to break the cycle, because the cycle is directly self-referential. 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.

  • In the "outside" situation, the reference is a. There is a retain cycle so long as your a reference is not set to nil. But you eventually do set it to nil, which is sufficient to break the cycle.

(Illustrations come from Xcode's memory graph feature. So cool.)

Alex

What causes the retain cycle is that you reference self in the closure.

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.

In the final example, you make it reference self again in the closure, that is why it does not deallocate. 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

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.

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

As the SIL documentation says, when you capture a local variable in a closure, it will be stored on the heap with reference counting:

Captured local variables and the payloads of indirect value types are stored on the heap. The type @box T is a reference-counted type that references a box containing a mutable value of type 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? instance (due to the fact that it's being captured by the closure), which in turn has a reference to the instance of A.

However, you then say:

a = nil

Which sets the heap-allocated boxed 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.

Just letting a fall out of scope without assigning a = nil will not break the reference cycle, as the instance of A? on the heap is still being retained by the closure property of A, which is still being retained by the A? instance.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!