What happens to Dispatch Queues when UIViewController is Deallocated?

旧街凉风 提交于 2019-11-30 05:26:31

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 DispatchQueues 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.

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