Is the [unowned self]
self here necessary?
Not only is the use of [unowned self]
not necessary, but it's very dangerous in an asynchronously dispatched block. You end up with a dangling pointer to a deallocated object.
If you don't want to keep keep a strong reference to self
in an asynchronous call, use [weak self]
, instead. You should only use unowned
if you know the block can never be called after self
is deallocated. Obviously, with async call, you don't know this, so [unowned self]
should not be used in that context.
Whether you use [weak self]
or use strong references is a question of whether you need the asynchronously executed block to keep a strong reference to the object in question or not. For example, if you're updating a view controller's view's controls only, then [weak self]
is fine (no point in updating a view that has been dismissed).
The more critical use of weak
and unowned
references is to avoid strong reference cycles. But that doesn't apply in the example you've provided. You only need to worry about those cycles if the view controller keeps some reference to the blocks itself (e.g. you have some closure property) and those closures reference self
, but without a weak
/unowned
qualifier.
My question is what happens to DispatchQueue
s when a view controller is deallocated?
Those queues will continue to exist, as will any dispatched blocks, until (a) all dispatched blocks finish; and (b) there are no more strong references to the queue.
So if you asynchronously dispatch blocks with weak
references to self
(i.e. the view controller), they will continue to run after the view controller is released. This is why it's critical to not use unowned
in this context.
For what it's worth, empirical tests can be illuminating. Consider:
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue(label: "com.domain.app.SecondViewController")
for i in 0 ..< 10 {
queue.async { [weak self] in
print("closure \(i) start")
self?.performSomeTask(i)
print("closure \(i) finish")
}
}
}
private func performSomeTask(_ value: Int) {
print("performSomeTask starting \(value)")
Thread.sleep(forTimeInterval: 5) // you wouldn't generally `sleep`, but merely for diagnostic purposes
print("performSomeTask finishing \(value)")
}
deinit {
print("deinit SecondViewController")
}
}
If you dismiss this view controller while the dispatched blocks are queued up and running, you'll see:
With [weak self]
, the view controller is retained only until the current dispatched block finishes, the view controller will then be released, and the rest of the blocks will rapidly fire off, but because of [weak self]
, the performSomeTask
won't run after the view controller is dismissed.
If you replace weak
with unowned
(and obviously remove the ?
in self?.performSomeTask(...)
), you'll see it crash if you dismiss the view controller before the queued blocks have had a chance to start. This is illustrative of why [unowned self]
is so dangerous with asynchronous code.
If you simply remove [weak self]
altogether and let it use a implicitly strong reference to self
, you'll see it won't deallocate the view controller until all queued blocks finish.
[unowned self]
in their example code but this makes sense now. – Faddist