I need to wait for a bunch of network APIs to complete. How do I do that?

81 Views Asked by At

I am writing an app that needs to make a number of network API calls (which all complete asynchronously, and wait until they all complete before moving on to the next step in my code. How do I do that?

1

There are 1 best solutions below

5
Duncan C On

This question comes up a lot (And not just with network calls. There are plenty of other examples where you need to wait until a group of background tasks complete before doing something else.)

Thankfully, there is a nice simple solution built into Apple's GCD (Grand Central Dispatch) framework: DispatchGroups.

A dispatch group is a simple object that lets you track pending tasks on any number of threads, and execute a block of code once they are all completed. Using a DispatchGroup is simple. Assuming you want your tasks to run in the background and you want to be notified when they are completed, you could do something like this:

  • Create a DispatchGroup. Let's call it dispatchGroup
  • Each time you start an async task, call dispatchGroup.enter() to tell the dispatch group to wait for that task to complete
  • Each time you complete an async task, call dispatchGroup.leave() to tell the dispatch group that a task has completed.
  • While at least one task is pending, call one of the DispatchGroup notify functions, passing in the code that you want to be executed when the last pending task completes.

One form of notify() takes a completion closure. The other form takes an object of type DispatchWorkItem (which is just an object that holds a completion closure.)

There are also wait functions that cause the called thread to wait synchronously until all the tasks complete.

Here is some simple code showing how to submit several async tasks that will take an unpredictable amount of time, and run a closure once they are all completed. (For simplicity, this example just submits tasks to a background queue using asyncAfter() and random delay values:

    func doDispatchGroups( completionHandler: @escaping () -> Void) {
        let dispatchGroup = DispatchGroup()
        for i in 1...10 {
            dispatchGroup.enter()
            let delay = Double.random(in: 0.02...0.2    )
            print("  Submitting job \(i).")
            DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
                print("  Job \(i) completed")
                dispatchGroup.leave()
            }
        }
        print("Waiting for jobs to complete")
        dispatchGroup.notify(queue: DispatchQueue.main) {
            completionHandler()
        }
    }

A sample run of that function displays text like this to the console:

  Submitting job 1.
  Submitting job 2.
  Submitting job 3.
  Submitting job 4.
  Submitting job 5.
  Submitting job 6.
  Submitting job 7.
  Submitting job 8.
  Submitting job 9.
  Submitting job 10.
Waiting for jobs to complete
  Job 7 completed
  Job 2 completed
  Job 1 completed
  Job 6 completed
  Job 8 completed
  Job 9 completed
  Job 10 completed
  Job 3 completed
  Job 5 completed
  Job 4 completed
All tasks completed

The jobs are submitted to a background queue in numeric order. Because each one waits for a random amount of time they are completed in a random order.

The notify() queues a completion handler to run once the last pending task is completed and calls leave().

Note that you have to have at least one pending task running in your dispatch group before you call notify() or the notify call's closure/DispatchWorkItem fires immediately.