Reputation: 42662
Given the following code
@IBAction func buttonClick(_ sender: Any) {
recordLabel.text = "Perform some time consuming networking..."
let workItem = DispatchWorkItem {
// Perform some time consuming networking...
DispatchQueue.main.async {
// QUESTION: Is it possible, that the UI recordLabel is
// destroy/ invalid at this stage?
self.recordLabel.text = "Done"
}
}
DispatchQueue.global().async(execute: workItem)
}
which is
I was wondering, during UI updating, is there ever be a chance, that the UI is destroy, or no longer valid?
The reason is that, for Android ecosystem, they have tendency to "re-create" UI in the middle of user thread execution (See Best practice: AsyncTask during orientation change). This make the UI reference hold by the user thread no longer valid.
I was wondering, does iOS Swift exhibits similar behavior?
Upvotes: 1
Views: 195
Reputation: 437412
I was wondering, during UI updating, is there ever be a chance, that the UI is destroy, or no longer valid?
If the view controller in question has been dismissed, the view will be removed from the view hierarchy. But, the way you’ve written this, if the view controller has been dismissed, your code will update a view that isn’t visible any more. Worse, the memory associated with your view controller and its views be not deallocated until this dispatched block finishes. There’s no point in doing that.
So, if we were to use your code pattern, you might instead do:
@IBAction func buttonClick(_ sender: Any) {
recordLabel.text = "Perform some time consuming networking..."
let workItem = DispatchWorkItem { // use `[weak self] in` pattern here, too, if you reference `self` anywhere
// Perform some time consuming networking...
DispatchQueue.main.async { [weak self] in
// Update the view if it’s still around, but don’t if not
self?.recordLabel.text = "Done"
}
}
DispatchQueue.global().async(execute: workItem)
}
Or, more naturally,
@IBAction func buttonClick(_ sender: Any) {
recordLabel.text = "Perform some time consuming networking..."
DispatchQueue.global().async { // use `[weak self] in` pattern here, too, if you reference `self` anywhere
// Perform some time consuming networking...
DispatchQueue.main.async { [weak self] in
// Update the view if it’s still around, but don’t if not
self?.recordLabel.text = "Done"
}
}
}
It is worth noting that one generally doesn’t dispatch network requests to a global queue because networking libraries like URLSession
, Alamofire, etc., already perform their request asynchronously. So you wouldn’t do that async
dispatch to the global queue.
Likewise, if this network request was merely to update something for this view controller’s view, you might even cancel the network request when the view was dismissed. (Why bother continuing to do a network request merely to update a view that might no longer exist?) It depends upon the nature of the request.
Finally, when you get this immediate issue behind you, you might reconsider whether the view controller should be issuing network requests at all, as opposed to some other type of object. That’s well beyond the scope of this question, but something to reconsider in the long term.
But we can’t comment further on any of these observations without seeing what you’re doing inside this dispatch to the global queue.
Upvotes: 3
Reputation: 578
Theoretically it is possible that your app suspended or terminated by the system during this operation, or that the user navigates away from that view, potentially deallocating the object that owns the recordLabel (self). I would say it's good defensive practice to capture self as weak in this case.
DispatchQueue.main.async { [weak self] in
self?.recordLabel.text = "Done"
}
Generally it's better for maintainability have to have an object that abstracts the async calls, defining the work in terms of the job you want done. There are alternative APIs/patterns to DispatchQueue, when you come back to the code it will be easier to change and update if needed.
Upvotes: 1
Reputation: 32781
Referencing self
in your closure created a strong reference to that instance, which means that as long as the reference exists, the object will be alive. And the reference will exist as long as the closure that captures it is alive.
If not specified otherwise, e.g. via [weak self]
, or [unowned self]
, closures capture strongly by default any reference.
So you should be ok, memory-wise. Beware though, strongly capturing self
can lead to retain cycles. Your code doesn't have this problem at this moment, but you never know how the code evolves in the future.
Another question is why you'd want to keep a controller alive for maybe a long period of time? It might happen that by the time the network calls finish, the controller is no longer on screen, thus the UI operations are in vain.
On a side note, you could directly capture recordLabel
, as you only need that in your dispatch:
let workItem = DispatchWorkItem { [recordLabel]
// Perform some time consuming networking...
DispatchQueue.main.async {
recordLabel.text = "Done"
}
}
This will avoid the retain cycle, however it doesn't ensure that the controller will be kept alive, it only ensures that the label will still exist by the time the dispatch on main happens. But again, unless the controller does some extra business logic work besides updating UI, why would you need it alive for an indefinite period of time?
Upvotes: 3
Reputation: 555
No, that recordLabel
will not be released from memory until its ViewController
get destroy.
But you should use [weak self]
when you update UI from main thread if your ViewController
may get destroy before networking is finished.
Upvotes: 1
Reputation: 16416
UI will not be deallocated until your view controller deallocs . When you create closure of DispatchWorkItem
and Dispatched main queue you retain the self inside. so it will increase the retain count of your view controller. so until your retain count of view controller is not getting 0 view controller will not be deallocated as view (IBOutlet).
Now If you add [unowned self]
in closure and before the execution of block your view controller deallocated for whatever reason like you dismiss or pop from navigation stack you will find a crash on line self.recordLabel.
because self is not retained inside the block. for [weak self]
it may work fine until you not forcefully unwrap self.
Upvotes: 3