DispatchGroup in Swift
Tags: ios, swift, async, gcd
Published: 2021-03-06 22:59:52

# Introduction

When we talk about async tasks in iOS development, we are talking about code that will run in a separate thread.

Why do we do this? Well, to start with, the main thread (represented by the main queue in [Grand Central Dispatch](https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2)) is the only thread that can run UI related code. You can't update a `UITableView` from a background thread, for instance. So, imagine what would happen if we wait in the main thread for a networking call to complete. If that is the only code that is running in the main thread, the app would freeze, resulting in a poor user experience.

That's why we delegate time consuming tasks (like waiting for a http call to complete) or computationally heavy tasks (like applying `CoreImage` filters) to background threads. This way, the main thread isn't blocked while those tasks are being executed, so the user can continue interacting with the app while we do the heavy work in the background.

After the async task completes, we can update our UI or do whatever we need with its result.

![ASync-Page-2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjoieohiqye0bw2ki1eb.png)
 
# Sync vs Async

Coding in async contexts is more challenging than working purely with sync tasks.
Imagine we have a (nonsense) function to print something in the console:

```swift
func say(_ text: String) {
    print(text)
}
```

And imagine we want to say `"Hi"` then a couple of phrases and finally `"Goodbye"`. Doing that in sync code is pretty simple and understandable:

```swift
say("Hi")
say("I love cookies")
say("My dog is called Emma")
say("I develop iOS apps")
say("Goodbye")
```

Pretty simple. Each line is written after the other and they follow the order in which we have written them.

Now, let's write an async version of say:

```swift
func say(_ text: String, completion: @escaping () -> Void) {
    let delay = Double.random(in: 1...2)
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
        print(text)
        completion()
    }
}
```

This code does exactly the same but does two other things in addition:

1. It prints the text after a delay. We use `DispatchQueue.main.asyncAfter(...)` to introduce that delay. This way, the function is now async. Note that also the delay is randomly generated, which means that we don't know exactly how long it is going to run.
2. We also pass a completion, which is a closure that will be executed when the function finishes.

Now, why is this type of code hard in the first place? Well, there are many challenges that we will have when working with async code. We will left thread safety for a future article and will describe two possible problems with async code.

## Running tasks serially

The first problem is how to simulate the synchronous behavior using asynchronous functions. With synchronous behavior I mean running an async task, waiting for its completion and then run another async task using the first task's result. 
This is a pretty common scenario when working with http requests. Maybe we need to execute a first request to login a user and then another request to get its profile details, and then another request to load other information about them, etc. You get the idea. Without performing login, the rest of the http calls couldn't have been executed.

![Async-001](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apo1qybsfirep4g06tcb.png)

For now, the code that can solve this, but that is not optimal in any way this by calling the second function in the completion from the first function. So, in our case, this would be something like this:

```swift
say("Hi") {
    say("I love cookies") {
        say("My dog is called Emma") {
            say("I develop iOS apps") {
                say("Goodbye") {}
            }
        }
    }
}
```

You get the problem here, right? This type of code is commonly called the pyramid of doom, and [it doesn't only happen in async code](https://levelup.gitconnected.com/escape-the-pyramid-of-doom-c58edd326225), so beware of it.

The most typical way to solve this problem is by using Combine's [`Future`](https://www.donnywals.com/using-promises-and-futures-in-combine/) or [PromiseKit's](https://github.com/mxcl/PromiseKit) `Promise`, or by using RxSwift's [`flatMap`](https://stackoverflow.com/questions/49627902/rxswift-map-and-flatmap-difference). If you're interested on how this works under the hood, I plan to write another article for this case, but for now you can check my code for a simple `Promise` in [this gist](https://gist.github.com/fmo91/40fcd72388a5ccab720ab21fdcaebc3f), or check [this quite old but incredibly mind blowing talk from Javi Soto](https://academy.realm.io/posts/swift-summit-javier-soto-futures/).

## Running tasks in parallel

The second common problem you'll face when working with async code is running tasks in parallel. If we aren't worried about the order in which the async code is executed, we can run all of the async tasks in parallel and finally run something as a completion. 

![ASync-Page-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1qhjooeqfssc0ym5ntr.png)

HTTP calls are also a common place where this could happen. Imagine we are accessing to a profile screen for a user. In this case we will probably need to call endpoints for their pictures, posts, personal information, among many others. Does it make sense to wait for the pictures to load before loading their personal information? Of course not. What makes sense in this case is to fire all of the tasks in parallel. After that, we can remove a loading indicator or whatever. 
In our example, we can say `"Hi"` synchronously, then all the other phrases (`"I love cookies"`, `"My dog is called Emma"` and `"I develop iOS apps"`) in parallel. And after that, we can say "Goodbye".

If you're working with an async programming library, this is also a common scenario where you are surely covered. In Combine you have [`zip`](https://developer.apple.com/documentation/combine/publishers/combinelatest/zip(_:_:)-7g8jo). A similar [`zip`](http://reactivex.io/documentation/operators/zip.html) function is present in RxSwift. In PromiseKit you can use [`when`](https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#waiting-on-multiple-promises-whatever-their-result). 

Their implementation involves a lot of advanced Swift. In this article, we'll explore another, simpler way of dealing with parallel async code.

# Introducing `DispatchGroup`

Well, here is our simple solution. `DispatchGroup` is an incredibly useful and simple to use class. It represents a group of asynchronous operations and has a few important methods:

- `enter()`: By calling `enter`, we are saying the `DispatchGroup` that an async task has began.
- `leave()`: `leave` means that an async task has ended.
- `notify(queue:)`: we can use `notify` to configure a closure that will be executed once all the tasks that entered into the `DispatchGroup` have already left it. There is also a synchronous version of `notify` that is called `wait(timeout:)`, which blocks the current thread until all the tasks that entered into the `DispatchGroup` have left it.

Enough theory, let's see how simple this can be in practice.

First, we need to initialize a `DispatchGroup`:

```swift
let group = DispatchGroup()
```

Pretty simple. Next, we will need to enter into the group when a task has began and leave the group when it has ended. So, for instance, if I'm going to say `"I love cookies"`, I will do this:

```swift
group.enter()
say("I love cookies") {
    group.leave()
}
```

And finally, we will configure `notify` with `.main` queue:

```swift
group.notify(queue: .main) {
    print("Goodbye")
}
```

That code will be executed only when every other task has left the group.

So, the complete code for this is:

```swift
let group = DispatchGroup()

print("Hi")

group.enter()
say("I love cookies") {
    group.leave()
}

group.enter()
say("My dog is called Emma") {
    group.leave()
}

group.enter()
say("I develop iOS apps") {
    group.leave()
}

group.notify(queue: .main) {
    print("Goodbye")
}
```

This means that we will say `"Hi"`, then the other three phrases will be printed at any time after that, because they are executed in parallel, and finally we will say `"Goodbye"`.

Of course this is just an example. The `say` method could also be a networking call, or a database operation, or whatever other real-world async task.

# To sum up

We have defined what async code is, and two basic issues we could have when working with it: running tasks serially and in parallel. For running tasks in parallel we saw that `DispatchGroup` can be a simple solution for that.
I will recommend using a library for this though. What I taught you here is basic async knowledge, but in real world scenarios, this won't be this straightforward, so using a battle tested library is always an advantage when working in production. I personally recommend using Apple's [Combine](https://developer.apple.com/documentation/combine) if you can (it is supported in iOS 13 and above) or using [RxSwift](https://github.com/ReactiveX/RxSwift) otherwise
Finally, I can't finish this article without mentioning async-await. With async-await, probably all of this discussion will be a thing of the past. It lets us write async code that can be written like sync code. You can see the proposal (already implemented and that will be released in the next version of swift) [here](https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md), and even try it in your code following a tutorial like [this one](https://peterfriese.dev/async-await-in-swiftui/).