Using DispatchGroup in Swift for asynchronous loops

Occasionally in an iOS app you’ll do something asynchronous, calling a completion handler when it’s done, like this:

itemProvider.loadObject(ofClass: UIImage.self) { image in
  onComplete(image)
}

This works great, but what if we have an array we need to loop through, performing an asynchronous action for each one? You could try this:

var images = [UIImage]()
for itemProvider in itemProviders {
  itemProvider.loadObject(ofClass: UIImage.self) { image in
    images.append(image)
  }
}
onComplete(images)

..but that won’t work, because the onComplete handler will fire before the asynchronous blocks execute.

For situations like this, we can use Swift’s DispatchGroup.

We create a DispatchGroup before starting the loop, enter it each time the loop iterates, and leave it when each iteration ends. Finally, we notify the dispatch group that everything is complete. Let’s see how that looks in practice:

let dispatchGroup = DispatchGroup()
var images = [UIImage]()
for itemProvider in itemProviders {
  dispatchGroup.enter()
  itemProvider.loadObject(ofClass: UIImage.self) { image in
    images.append(image)
    dispatchGroup.leave()
  }
}

dispatchGroup.notify(queue: .main) {
  onComplete(images)
}

Now, everything works as it should and the completion handler isn’t executed until after the loop has finished iterating.

Thanks for reading.

To get in touch, email me or find me on Mastodon or Twitter.

If you liked this post, you'll love my iOS apps. Check out 🥇 Personal Best, 🎵 Taylor's Version, and 🐷 SalaryPig.