I'm experimenting the retain cycle in closure like following
class Sample {
deinit {
print("Destroying Sample")
}
func additionOf(a: Int, b:Int) {
print("Addition is \(a + b)")
}
func closure() {
dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
self?.additionOf(3, b: 3)
usleep(500)
self?.additionOf(3, b: 5)
}
}
}
Later at some point, I'm doing
var sample : Sample? = Sample()
sample?.closure()
dispatch_async(dispatch_get_global_queue(0, 0)) {
usleep(100)
sample = nil
}
The output will be
Addition is 6
Destroying Sample
This is understandable because self
is nil out before doing self?.additionOf(3, b:5)
If I made a change inside the closure by creating another variable which references to [weak self]
like following
dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.additionOf(3, b: 3)
usleep(500)
strongSelf.additionOf(3, b: 5)
}
This time, the output is
Addition is 6
Addition is 8
Destroying C
My question is why strongSelf
is not nil out after sample = nil
. Is it because it is captured inside the closure before sample = nil
Let's consider the contemporary equivalent to your second example:
DispatchQueue.global().async { [weak self] in
guard let self else { return }
self.additionOf(3, b: 3)
Thread.sleep(forTimeInterval: 0.0005)
self.additionOf(3, b: 5)
}
This is a common pattern and it effectively says “if self
has been deallocated, return
immediately, otherwise establish a strong reference to self
, and maintain this strong reference until the closure finishes.”
So, in your example, if self
was not nil
when this dispatched block started, so as soon as we hit the guard
statement, we now have two strong references to this Sample
object, both the original sample
reference and this new reference inside this closure.
So, when your other thread removes its own strong reference, sample
, to this Sample
object (either by falling out of scope or by explicitly setting sample
to nil
), this closure’s strong reference is still present and will keep the object from being deallocated (or at least not until this dispatched block finishes).
In your comment above, you ask:
I still not understand why
strongSelf
does not get changed since self isnil
out...
Strong references never get set to nil
just because some other strong reference (even if it was the original strong reference) was set to nil
. That behavior of a reference getting set to nil
only applies to weak
references, not to strong references.
When your code performs sample = nil
on the first thread, all that does is remove a strong reference. It does not delete the object or anything like that. It just removes its strong reference. And now that the dispatched block has its own strong reference, all that happens upon sample = nil
is that the object that had two strong references now only has one strong reference left (the reference to it in the dispatched block). Only when this final strong reference is removed will the object be deallocated and deinit
will be called.
FWIW, the [weak self]
capture list actually does not have much utility if you are immediately dispatching to the global queue. You would probably excise it and just do:
DispatchQueue.global().async { [self] in
additionOf(3, b: 3)
Thread.sleep(forTimeInterval: 0.0005)
additionOf(3, b: 5)
}
The [weak self]
capture list is most useful if the closure is running at some later point in time, e.g., a completion handler or asyncAfter
, where there is a practical chance that self
might fall out of scope before the closure runs:
DispatchQueue.global().asyncAfter(deadline: .now() + 3) { [weak self] in
guard let self else { return }
self.additionOf(3, b: 3)
Thread.sleep(forTimeInterval: 0.0005)
self.additionOf(3, b: 5)
}