Swift closures [weak self] and async tasks

2019-02-04 20:05发布

问题:

Imagine a situation, when you want to asynchronously load some text from the server and display the result in the ViewController's UITextField.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {

    //... some long running async operation

    if let textResponse = responseFromServer {
        dispatch_async(dispatch_get_main_queue(), { [weak self] () in
            self?.textField.text = textResponse
        })
    }
})

A.) Do I need to use [weak self] within the closure used for async calls?

I thought I need to, but I am not sure after I read some Q/A here at StackOverflow and went through quite a few open source apps that don't use [weak self] for async tasks + closures.

i.e.:

The only time where you really want to use [unowned self] or [weak self] is when you would create a strong reference cycle. (Shall we always use [unowned self] inside closure in Swift)

There is no strong reference cycle in my case.

or:

But to be clear, it would still be best to use a strong reference in this circumstance. (Swift ARC and blocks)

B.) Let's say it's good to go with the strong reference. What happens to the ViewController when the user navigates to the different page in the middle of async loading? Would it keep the invisible ViewController in the app memory until the async task finishes?

回答1:

There is no strong reference cycle (retain cycle) here. If you employ a strong reference to self, it is resolved as soon as the dispatch block runs. You theoretically could use strong reference here if you needed to.

Having said that, I would advise using a weak reference in this case. There's no point in maintaining a strong reference for the duration of the time consuming process solely for the purpose of updating a text field for a view that has already been dismissed. If you were updating other model objects or the like, perhaps you might need to keep the strong reference, but you don't need to do so in this case. As a general principle, one should release memory as soon as reasonably possible.

Even better, I'd also look at the "long running async operation" and decide whether I really want it to continue to run after the view controller has been dismissed. If not, I'd be inclined to also make the request cancelable and then have deinit cancel the request. And, in that case, you would definitely want to use weak reference (or else deinit wouldn't be called until the long running async operation finishes).