A Deep Dive Into Dispatch Groups in Swift
Tags: iOS, Xcode, Swift, Programming, Mobile

Dispatch Groups: Demo

In our demo, we’ll perform the following steps:

Call the GitHub trending-users API and get the top three users’ profile image URLs. Load the three profile images using URLs we got in step 1 and cache them. Go back to the main thread when the above operations are complete.

Note: I’m taking a real-world example to explain all the nuances that you might face when you include this in your app.

Essentially, we’re going to make four API calls: one to get trending users and three to fetch images.

First, we’ll look at how to perform these operations using the dispatch group’s wait() method, and then we’ll look at the notify() variation.

Code Setup

This is a piece of code that we’re going to use for both the wait() and notify() variations.

Image load setup

The loadImage method takes the imageURL as an input, fetches the image, and caches in a simple image cache. It then executes the completion closures.

wait()

Let me explain what’s going on.

Inside the getUserImages method, we initialize a DispatchGroup object. We configure the dataTask to fetch the trending users and to call the loadImages once we get the trending user’s response. We add group.enter() before calling the trending-users API and add a defer statement, group.leave() , which will run when we exit the completion closure of dataTask . Once we receive the trending API response, we call the loadImage method to get the images. We add group.enter() before calling the method and group.leave() just before leaving the closure. In total, we add three enter and leave calls here .resume() triggers the data task. All we did until now is just configuration, but the actual call to the service happens now. group.wait() will block the caller thread until all the operations are complete (it’ll determine this by equating the number of enter() and leave() calls it has encountered). Once all of the operations are complete — i.e., four group.leave() s are executed — the calling thread will be unblocked and work done will be printed on the calling thread. Let's see the output of this:

After all of the closures are executed, 'work done' is printed using the calling thread.

Common pitfalls

Usually in scenarios like the above example, we don't add group.enter() and group.leave() around the main API call (trending-users call) because the whole purpose of adding a dispatch group is to keep track of the concurrent calls happening to fetch images.

However, in this case, we have to add it around the trending API call; otherwise, the work done block will be executed even before the image-fetch calls are made.

Why? Since the trending API call will be delegated to a background thread, the execution of the calling thread will reach dispatchGroup.wait() , and by then, image fetch enter() or leave() calls aren’t made. So the dispatchGroup.wait() won’t block the main thread and will execute the print statement.

Avoid wait()

It’s highly recommended to avoid wait() whenever possible because it essentially blocks the calling thread, which is the main thread in our case.

Even if we add the entire getUserImages function in a concurrent queue and make it work on a background thread, we’re blocking one of the very limited GCD worker threads while we perform the request. This is why we use notify() .

Notify()

We initialize the dispatch group inside the closure of the trending API call. We add group.enter() before calling the method and group.leave() just before leaving the closure. In total, there are three enter and leave calls here. Then we encounter the group.notify() method, which doesn't block the calling thread and instead notifies the main thread when all the group.leave() methods are executed. Now the loadImage closure will be executed, and images will be stored successfully. Once all the closures are executed, the main thread is notified and work done will be printed on the main thread.

When using a dispatch group, you should always consider using notify() as it doesn't block threads.

Bonus

We can set a timeout on the wait() method, and this would enable us to set a time from which we’ll block the calling thread. We’ll unblock the thread and start executing other tasks even if all the leave() methods are not executed.

method, and this would enable us to set a time from which we’ll block the calling thread. We’ll unblock the thread and start executing other tasks even if all the methods are not executed. We can use DispatchWorkItem with the notify() method, and this would help us implement cancellation handlers and event handlers effectively.

EndNote

That’s all I have for this article, thanks for reading. Follow me on twitter for more updates. Provide some claps if you liked the post as that would encourage me to write more(you can give up to 50). P.s. Don’t worry at all about being pedantic. If you see that I can improve something in my article, do let me know.