Execution order of async tasks on the main queue - ios

In the code below the print statements execute in the order of the numbers they contains (1, 2, 3, etc.)
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
print("4")
}
print("1")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.main.async {
print("5")
}
print("2")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
print("6")
}
print("3")
}
Can you explain why execution process happens this way? Are viewWillAppear and viewWillAppear methods already in the queue when we asynchronously dispatch a block in viewDidLoad?

Are viewWillAppear and viewDidAppear methods already in the queue when we asynchronously dispatch a block in viewDidLoad?
In effect: yes. They are not "in the queue", exactly, but they are already in train as part of the existing main queue code sequence.
It's all a matter of when the runloop comes to an end. Your code dispatched asynchronously to the main queue cannot run until that happens. In effect, we have to come completely to the end of this CATransaction (one "revolution" of the runloop) before print 4 and so forth can happen.
You have probably put this logging into the root view controller. It is a special case, because your app is just launching and nothing happens until the call to makeKeyAndVisible is sent to the window.
At that moment viewDidLoad is called and viewWillAppear is called, in succession, as part of this one call to makeKeyAndVisible.
Thus, the same code is still running on the main thread; there has been no moment for your dispatched code to run. So we get print 1 and print 2 before anything else.
The situation with viewDidAppear is a little different:
As you can see, we are no longer in that call to makeKeyAndVisible. But the main thread is still running, because we turn immediately to any transaction completion blocks (cleanUpAfterCAFlushAndRunDeferredBlocks). It might help to think of viewDidAppear as being effectively the completion block to the animation in viewWillAppear. That has already been configured as part of the call to viewWillAppear, so it is still part of the same transaction. Thus viewDidAppear still comes right after viewWillAppear without pause and we get print 3.
Now at long last the launch sequence ends and your main queue asynchronous code has a chance to run, which it does in the order in which it was enqueued (print 4, print 5, print 6).

Related

Swift: Update UI - Entire function on main thread or just the UI update?

I've read that the UI should always be updated on the main thread. However, I'm a little confused when it comes to the preferred method to implement these updates.
I have various functions that perform some conditional checks then the result is used to determine how to update the UI. My question is should the entire function run on the main thread? Should just the UI update? Can / should I run the conditional checks on another thread? Does it depend on what the function does or how fast you want it done?
Example a function that changes the image inside an ImageView without threading:
#IBAction func undoPressed(_ sender: Any) {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
topImageView.image = lastDrawing
}
else {
// empty
topImageView.image = nil
}
}
}
Should I be setting topImageView.image on the main thread? Like this:
#IBAction func undoPressed(_ sender: Any) {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
DispatchQueue.main.async {
self.topImageView.image = lastDrawing
}
}
else {
DispatchQueue.main.async {
self.topImageView.image = nil
}
}
}
}
Should I be using a background thread for the conditional checks? Like this:
#IBAction func undoPressed(_ sender: Any) {
DispatchQueue.global(qos: .utility).async {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
DispatchQueue.main.async {
self.topImageView.image = lastDrawing
}
}
else {
DispatchQueue.main.async {
self.topImageView.image = nil
}
}
}
}
}
If someone could explain what method is preferred and why that would be really helpful.
Back up. Except in special circumstances, all your code is run on the main thread. UIAction methods, for example, are ALWAYS executed on the main thread, as are all the methods defined by UIViewController and it's various subclasses. In fact, you can safely say that UIKit methods are performed on the main thread. Again, your methods will only be called on a background thread in very special circumstances, which are well documented.
You can use GCD to run blocks of code on background threads. In that case, the code is being run on a background thread because you explicitly asked for that to happen.
Some system functions (like URLSession) call their delegate methods/run their completion handlers on background threads by default. Those are well documented. For third party libraries like AlamoFire or FireBase, you'll have to read the documentation, but any code that's called on a background thread should be very well documented because you have to take special precautions for code that runs on a background thread.
The usual reason to use a background thread is so that a long-running task can run to completion without freezing the user interface until it's done.
A common pattern for, example, is using URLSession to read some JSON data from a remote server. The completion handler is called on a background thread since it might take time to parse the data you get back. Once you are done parsing it, though, you'd wrap a call to update the UI in a GCD call to the main thread, since UI changes must be performed on the main thread.
First off, your undoPressed method will be called on the main queue.
In the first set of code, everything will be on the main queue.
In the second set of code, using DispatchQueue.main.async is pointless since the rest of the code is already on the main queue.
So really your only two sensible options are 1 and 3.
Given your code, option 1 is fine. You would only want to use option 3 if the code being run in the background took more than a trivial amount of time to execute. Since the code you have here is trivial and will take virtually no time to execute, there is no point in option 3 here.
So simply use your first set of code and you'll be fine.
Worry about moving code to the background when it need to perform a big loop or calculate a complicated algorithm or perform any sort of network access.
To make it simple, make the calculation and then everything related to that updated calculation that needs to be reflected in the UI should be done from:
DispatchQueue.main.async{ //code }
that is using main thread.

Correct way to cancel an AsyncTask in swift

I use DispatchQueue.main.async in UIViewController and i need to cancel async task when dismiss UIViewController
Grand Central Dispatch does not allow tasks to be cancelled from the outside when already running.
You basically have to check inside the asynchronously running task if your view controller still exists.
Assuming your call to GCD is directly in your UIViewController, so self refers to that view controller:
DispatchQueue.global().async { [weak self] in
// Do work
// Check if self still exists:
guard let _ = self else {
return // cancels the task
}
// Continue working
}
As self is only a weak reference to your view controller it will not stop the view controller from getting deallocated when dismissed. When it gets deallocated, self inside your GCD block becomes nil and you known that you can stop your task.
So you just have to check if self is nil every once in a while in your asynchronous block.
Note: Do not perform long running tasks on the main queue but in a global queue or even a private queue. Using the main queue blocks the main thread from performing other work like UI updates so your app freezes.

Swift: feedback when function ends

Let's say I have a ViewController A and a class B.
when I press some button inside A, it calls an IBAction that calls a function B.foo() which returns an Int
B.foo() takes 8~10 seconds to finish and while it runs I'd like to put an Loading... animation on A, and when B.foo() finishes, the animation would stop.
How can I do this? this is an pseudo-code example of what I wish:
#IBAction func buttonPressed(_ sender: UIButton){
UIView.animate(blablabla......)
DO({
self.answer = B.foo()
}, andWhenItFinishesDo: {
self.someone.layer.removeAllAnimation()
})
}
This is a very common problem. One way to solve it would be to use different queues (You can think of them as lines of work that can happen in parallel).
The the basic idea is that once your button is pressed, you show your loading indicator and "dispatch" the long work to a secondary queue, that will operate in the background and do the work. This ensures that your main queue does not block while the work happens and the user interface stays responsive.
The trick is now that you want to get notified when the long work is finished so that you can stop showing the loading indicator (and possibly do even more).
While you actually could use some kind of notification system, there are other, sometimes more appropriate ways. It would actually be even more convenient, if you could just tell the long running function to call you back specifically with code that you provide.
That would be the basic concept of a "completion handler" or "callback".
The whole thing would look something like that:
// Some made up class here
class B {
// This is only static because I do not have an instance of B around.
static func foo(completion: #escaping (Int) -> Void ) {
// The method now does all of its work on a background queue and returns immediately
DispatchQueue.global(qos: .background).async {
// In the background this may take as long as it wants
let result = longCalculation()
// VERY important. The caller of this function might have a certain
// expectation about on which queue the completion handler runs.
// Here I just use the main queue because this is relatively safe.
// You could let the caller provide a queue in the function
// parameters and use it here
DispatchQueue.main.async {
// The completion handler is a function that takes an Int.
// That is exactly what you are providing here
completion(result)
}
}
}
}
#IBAction func buttonPressed(_ sender: UIButton){
self.showLoadingIndicator()
// The foo function now takes a completion handler that gets the result in.
// You have to provide this function here and do something with the result
//
// The completion handler will only be run when the foo function calls it
// (which is after the computation as you can see in the method above.
//
// I am also telling the completion handler here that self should not be
// held on to as the view controller might already have gone away when the
// long calculation finished. The `[weak self]` thingy makes that inside
// your completion handler self is an optional and might be nil (and it
// doesn't hold a strong reference to self, but that's a whole other topic)
B.foo(completion: { [weak self] result in
// Do something with the result
// Since we are called back on the main queue we can also do UI stuff safely
self?.hideLoadingIndicator()
})
}
I hope this helps a bit.
Asynchronous programming can be quite difficult to learn but there are tons of tutorials and examples you can find on this topic.
Hey Hamish you can do this in two simple ways,
First one is using the defer statements provided for functions.
Defer statement block is executed after the functions goes out of scope.
here is a simple example to describe the same.
func print1000000() {
//start displaying the loading indicator
defer {
// hide the loading indicator and move to the next ViewController
let seVC = storyboard?.instantiateViewController(withIdentifier: "SecondVC") as! SecondVC
self.navigationController?.pushViewController(seVC, animated: true)
}
// here goes the task you want to execute such as downloading a file or the one i did here
for index in 0...1000000 {
print(index)
}
}
The above function prints numbers upto 1000000 and then pushes the control to another ViewController
=========================================================================
Second way of doing it is by using closures, as described by Thomas in his answer.

DispatchQueue.main.asyncAfter method freeze UI in a particular call

I'm calling the DispatchQueue.main.asyncAfter method after I modify a property of an object (I'm firing it with KVO). I'm only showing a confirm view for a second and then run a block which do another things on the UI (pops a viewcontroller). The problem is that when i call this from a UICollectionViewCell works fine, but when i call this method from another particular UICollectionViewCell in my app, it freezes up. The method is implemented in a extension of UIView.
public func showConfirmViewWith(title: String!, frame: CGRect? = screenBounds, afterAction: (() -> Void)?) {
let confirm = ConfirmationView(frame: frame!, title: title)
self.addSubview(confirm)
confirm.checkBox?.setCheckState(.checked, animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1) ) {
if afterAction != nil {
afterAction!()
}
confirm.removeFromSuperview()
}
}
When it freezes the code inside the Dispatch never gets called, I have a breakpoint inside, but never gets to that point.
UDPATE
Here's the stack trace when i pause the execution:
First time I paused the execution
I solved this tricky bug. First thing, there wasn't any problem with the DispatchQueue.main.asyncAfter call. The app was going into a loop and wasn't any signs that was lock. I ran the profiler and saw that the layout of some constraints was being called over and over. I have a tableView inside the collectionViewCell, and an animation when finish the call to asyncAfter. The problem was that the overriden method layoutSubviews has a tableView.reloadData() inside, so everytime i was trying to exit after the async call with an animation, the layoutsubviews was being called and the reloadData() triggered over and over again. I'm not sure why that reloadData was there, but seems to be working fine now.

Perform animation while performing segue

From my initial View Controller, I want to be able to segue to another view controller. This second view controller, however, takes a few seconds to load, and I want to have my source view controller display an animation while it loads.
Performing the segue blocks the rest of the code from running, but I can't put it in a background thread because it technically updates the UI (Xcode gave me lots of warnings when I tried).
The animation also updates the UI, which means I can't put that in its own separate thread either.
Is there another method I could use to accomplish this?
class LaunchViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(5, animations: {
//Perform animation that updates the UI
}, completion: nil)
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("seg", sender: self)
}
}
P.S. When I put performSegueWithIdentifier in a background thread, it worked perfectly. But like I said, I get lots of errors (and Xcode will throw an exception in a coming release).

Resources