Swift 3 brought many changes, including a much needed syntax revamp for Grand Central Dispatch, Apple’s multithreading framework. If you’re familiar with threading, it’s a breeze to use - DispatchQueue.main.async
and away you go. In this post we’ll explore a class called DispatchGroup
, which isn’t so often used, but can be incredibly helpful.
What does a Dispatch Group do?
Let’s say you’ve got several long running tasks to perform. After all of them have finished, you’d like to run some further logic. You could run each task in a sequential fashion, but that isn’t so efficient - you’d really like the former tasks to run concurrently. DispatchGroup
enables you to do exactly this. Let me provide some solid examples:
- You need to run two distinct network calls. Only after they’ve both returned do you have the necessary data to parse the responses.
- An animation is running, parallel to a long database call. Once both of those have finished, you’d like to hide a loading spinner.
- The network API you’re using is too quick (oh, if only this was always the case). This is a little more nuanced, but now your pull to the refresh gesture doesn’t seem to be working, even though it is. The API call returns so quickly that the refresh control dismisses itself as soon as it has finished the appearance animation - which makes it seem like it is not refreshing. To solve this, we can add a faux delay. i.e. we can wait for both some minimum time threshold, and the network call, before hiding the refresh control.
Great, so how do I use it?
Let’s start with a simple example.
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
longRunningFunction { dispatchGroup.leave() }
dispatchGroup.enter()
longRunningFunctionTwo { dispatchGroup.leave() }
dispatchGroup.notify(queue: .main) {
print("Both functions complete 👍")
}
In the above, both long running functions will perform concurrently, followed by the print statement, which will execute on the main thread. Each call to enter()
must be matched later on with a call to leave()
, after which the group will call the closure provided to notify()
. We must call at least one enter prior to calling notify, so that the group has something blocking it - without this, the notify closure would run before we call enter()
. Finally, notice how we specify only .main
for the queue? Here, you can provide any queue object you wish (.main
is really DispatchQueue.main
, not some sort of enum).
A few handy tricks
The above model should provide a great starting point, however DispatchGroup
has a few other tricks up its sleeve:
- Instead of
notify()
, we can callwait()
. This blocks the current thread until the group’s tasks have completed. - A further option is
wait(timeout:)
. This blocks the current thread, but after the timeout specified, continues anyway. To create a timeout object of typeDispatchTime
, the syntax.now() + 1
will create a timeout one second from now. wait(timeout:)
returns an enum that can be used to determine whether the group completed, or timed out.
And there you have it - DispatchGroup
in a nutshell. Happy (asynchronous) coding.