Sometimes you have a need to wait on several asynchronous tasks before doing something in your code. DispatchGroup is a powerful API that lets you group together these tasks into one task.
DispatchGroup allows for aggregate synchronization of work. You can use them to submit multiple different work items and track when they all complete, even though they might run on different queues. This behavior can be helpful when progress can’t be made until all of the specified tasks are complete.
For example you have a ViewController that shows data from three different APIs. You need data from all 3 API’s before you can render the page. One way to do this would be to chain network calls and render the page after the 3rd API completed. But this leads to ugly nest code and requires that the APIs be called synchronously, rather than asynchronously.
Let’s looks look at how to solve this problem by chaining first.
I’ll be using httpbin.org for these examples. It’s a great tool for testing HTTP requests and responses.
This URL allows you to make an HTTP request that will wait a duration in seconds. For example, this will take 10 seconds to load:
https://httpbin.org/range/1024?duration=10
I’m using this API so you can easily see the time difference.
I wrote a simple method called makeNetworkRequest(), which take a duration in seconds and uses NSURLSession to call httpbin.org:
func makeNetworkRequest(duration:Int, completion: @escaping (_ result: String?) -> Void) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let url = URL(string: "https://httpbin.org/range/1024?duration=\(Int(duration))")!
NSLog("makeNetworkRequest() \(url)")
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
NSLog(error!.localizedDescription)
completion(nil)
} else {
if let str = String(data: data!, encoding: String.Encoding.utf8) {
completion(str)
} else {
completion(nil)
}
}
}
task.resume()
}
Here is an example using nesting:
makeNetworkRequest(duration: 2) { (str) in
NSLog("Request #1 \(str ?? "nil"))\n")
makeNetworkRequest(duration: 3) { (str) in
NSLog("Request #2 \(str ?? "nil"))\n")
makeNetworkRequest(duration: 10) { (str) in
NSLog("Request #3 \(str ?? "nil"))\n")
NSLog("Done!")
}
}
}
As you can see this accomplishes a solution to our problem, but there is a lot of nested code and called the API’s synchronously. It took 15 seconds to wait for all 3 responses.
Now let’s try this with DispatchGroup. With DistpatchGroup you first create a DistpachGroup:
let group = DispatchGroup()
For each block of code you want to execute you call:
group.enter()
Before the API and then
Group.leave()
When it finishes executing.
Finially group.notifiy(…) will be called when all group.enter() calls have a successful group.leave() response:
group.notify(queue: DispatchQueue.global(qos: .background)) {
NSLog(“All 3 network reqeusts completed”)
completion(strings)
}
Here’s a complete example:
func makeNetworkRequests(completion: @escaping (_ result: String?) -> Void) {
let group = DispatchGroup()
var strings = "start"
group.enter()
makeNetworkRequest(duration: 2) { (str) in
NSLog("Request #1 \(str ?? "nil"))\n")
if let str = str {
strings = strings + str
}
group.leave()
}
group.enter()
makeNetworkRequest(duration: 3) { (str) in
NSLog("Request #2 \(str ?? "nil"))\n")
if let str = str {
strings = strings + str
}
group.leave()
}
group.enter()
makeNetworkRequest(duration: 10) { (str) in
NSLog("Request #3 \(str ?? "nil"))\n")
if let str = str {
strings = strings + str
}
group.leave()
}
group.notify(queue: DispatchQueue.global(qos: .background)) {
NSLog("All 3 network reqeusts completed")
completion(strings)
}
}
Notice how much easier this code is to read. No more nested blocks. It’s also asynchronous. It only takes 10 seconds for all 3 to return, because we’re only waiting on the longest API to respond.
For example code, see: https://github.com/dougdiego/iOSDemos