I was testing swift closure with Xcode playground.
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.
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. Why? is a not recycle reference?
To be test, I use a function to set the closure, which the code is version 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.
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. 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. This difference is manifested in what it takes to break the cycle:
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.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.)
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 typeT
.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With