Concurrency in modern Swift with Async & Await in SwiftUI — Part 1

DevTechie Inc
Jul 15, 2023

Concurrency in modern Swift with Async & Await in SwiftUI — Part 1

Apple introduced async and await keywords with the release of Swift 5.5 to modernize the concurrency APIs for Apple ecosystems.

Before Swift 5.5, Grand Central Dispatch (GCD), Operation Queues and Completion blocks were the preferred way to manage and create a sequence of concurrent code. With the introduction of async and await, we don’t have to deal with unmanagable nested completion blocks anymore.

Async and await make code much more readable. The idea is that an asynchronous function must be decorated with the async keyword and the calling function must add the await before calling the asynchronous function.

SwiftUI supports calling asynchronous functions out of the box with Task structure and task modifier.

In this article, we will explore async and await.


Code Execution

By default code executes line after line unless specified by the developer to distribute the work on different threads, and we can see this with an easy example.

struct DevTechieAsyncAwaitExample: View {
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("Code")
            }
            .onAppear {
                print("Let's work on something")
                print((1...2000).reduce(0, +))
                print("Work is done")
            }
            .navigationTitle("DevTechie")
        }
    }
}

Code written in the onAppear block is called sequentially, so running it will print the following in the console.

Let's work on something
2001000
Work is done

At this point we are running our code in the main thread. This works fine as long as the code executes fast enough that the user never notices a lag or unresponsive UI; but what if there is a task which takes minutes to run? In that case the UI will be frozen, and if the UI is frozen for a long time, its not only a bad user experience, but the system may decide to kill the unresponsive app.

So the best practice is to have long-running tasks run asynchronously.

We can use GCD to fix the issue.

struct DevTechieAsyncAwaitExample: View {
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("Code")
            }
            .onAppear {
                print("Before GCD Block")
                DispatchQueue.global().async {
                    print("Let's work on something")
                    print((1...2000).reduce(0, +))
                    print("Work is done")
                }
                print("After GCD Block")
            }
            .navigationTitle("DevTechie")
        }
    }
}

We added two additional print statements, one before the GCD block and another one right after. These print statements will tell us if we are truly running an async piece of code or not.

Results from console.

Before GCD Block
After GCD Block
Let's work on something
2001000
Work is done

Notice that the newly added print statements appear first in the console and print statements from the GCD block are printed a few moments later. This proves that our code inside the GCD is running asynchronously.

We can still continue to use the GCD, but starting iOS 15, we have a Swifty solution for doing the same with the struct called Task and its corresponding modifier task.

Let’s replace the GCD block with Task block next.

struct DevTechieAsyncAwaitExample: View {
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("Code")
            }
            .onAppear {
                print("Before Task Block")
                Task {
                    print("Let's work on something")
                    print((1...2000).reduce(0, +))
                    print("Work is done")
                }
                print("After Task Block")
            }
            .navigationTitle("DevTechie")
        }
    }
}

Our program will still run asynchronously.

Console results.

Before Task Block
After Task Block
Let's work on something
2001000
Work is done

This gives you a good picture of the type of work we are after. More in-depth discussions about Tasks will come later in the article, but for now, let's shift our focus to async and await in the next article.