Swift Async/Await: Conquer Concurrency
Swift’s Async/Await: Conquer Concurrency
Swift’s Async/Await: Conquer Concurrency

Swift has this cool built-in feature that lets you write code that can do multiple things at once, like handling short tasks while also tackling long ones. It’s like a multitasking champ! So When we talk about “concurrency” in this blog, we mean mixing asynchronous and parallel code to do multiple things at once. It’s like juggling tasks in code!
If you’ve dabbled in writing concurrent code before, you might have crossed paths with threads. But here’s the deal in Swift: while it’s built on threads, you don’t have to wrestle with them directly. Swift’s concurrency game is a bit more sophisticated. An async function can kindly step aside from its thread, allowing another async buddy to hop in while the first one is temporarily on hold. And when that first function gets back to work, Swift doesn’t play favorites when it comes to threads — it’s all about sharing the workload!
You can write concurrent code in Swift without using its special features, but it can get pretty tricky to read and manage. Take this code for instance
In this code, you first have to fetch the user’s location asynchronously. Once you have the location, you need to fetch weather data for that location. Finally, you extract the current temperature from the weather data and display it. Each step requires a completion handler, leading to nested closures. As your code becomes more complex with error handling, additional data processing, or multiple asynchronous calls, the nesting can become unwieldy and challenging to read and maintain.
Async/Await
An async function or method is like a chill version of regular functions. Unlike the usual ones that finish their job, throw errors, or never come back, async ones can hit pause in the middle if they’re waiting for something. In these async functions, you mark the spots where they can take a break while doing their thing. It’s like giving your code a breather when it needs it!
So, when you want to tell Swift that your function is one of those cool asynchronous ones, you simply throw in the “async” keyword in the function’s declaration, right after listing its parameters. It’s a bit like when you use “throws” to say a function can throw errors. And if your async function gives you something back, just stick “async” before that little arrow thing (->).
For example, here’s how you might fetch the userLocation:
When calling an asynchronous method, execution suspends until that method returns. You write await
in front of the call to mark the possible suspension point. This is like writing try
when calling a throwing function, to mark the possible change to the program’s flow if there’s an error. Inside an asynchronous method, the flow of execution is suspended only when you call another asynchronous method — suspension is never implicit or preemptive — which means every possible suspension point is marked with await
.
For example, the code below fetches userLocation, weather data and current temperature and then displays it:
let userLocation = await getUserLocation()
In this line, we declare a constant variableuserLocation
. It's assigned the result of thegetLocation()
function call, which is marked withawait
. Theawait
keyword indicates that this is an asynchronous operation, and it will pause execution until thegetUserLocation()
function completes. It's similar to saying, "Hey, we're waiting for the user's location."let weatherData = await getWeatherData(for: userLocation)
: Here, we declare another constant variableweatherData
. It's assigned the result of thegetWeatherData(for: userLocation)
function call. Again, theawait
keyword is used to signal that this is an asynchronous operation. It means we're waiting for the weather data based on the user's location.let temperature = await getCurrentTemperature(from: weatherData)
: In this line, we declare a constant variabletemperature
. It's assigned the result of thegetCurrentTemperature(from: weatherData)
function call. Once more,await
is used to indicate that this is an asynchronous operation. We're waiting to get the current temperature from the weather data.displayTemperature(temperature)
: Finally, we call thedisplayTemperature()
function, passing thetemperature
as an argument. This line displays the temperature to the user, assuming thattemperature
now holds the correct value.
While the code’s execution is suspended, some other concurrent code in the same program runs. For example, maybe a long-running background task downloading some files. That code also runs until the next suspension point, marked by await
, or until it completes.
The possible suspension points in your code marked with await indicate that the current piece of code might pause execution while waiting for the asynchronous function or method to return. This is also called yielding the thread because, behind the scenes, Swift suspends the execution of your code on the current thread and runs some other code on that thread instead.
But here’s the catch — you can only use await in specific places in your program:
- Inside the body of an asynchronous function, method, or property.
- Inside the static main() method of a structure, class, or enumeration that’s tagged with @main.
- Inside an unstructured child task( Task closure decalred as Task { //Code })
Calling Asynchronous Functions in Parallel
Calling an asynchronous function with await
runs only one piece of code at a time. While the asynchronous code is running, the caller waits for that code to finish before moving on to run the next line of code
For example to get the list of three different user locations, you could await three calls to the getUserLocation(user: User)
function as follows:
This approach has an important drawback: Although the getUserLocation is asynchronous and lets other work happen while it progresses, only one call to getUserLocation(user:)
runs at a time. Each user location is fetched before the next one starts fetching. However, there’s no need for these operations to wait — each user location can be fetched independently, or even at the same time.
To call an asynchronous function and let it run in parallel with code around it, write async
in front of let
when you define a constant, and then write await
each time you use the constant.
In this example, all three calls to getUserLocation(user:)
start without waiting for the previous one to complete. If there are enough system resources available, they can run at the same time. None of these function calls are marked with await
because the code doesn’t suspend to wait for the function’s result. Instead, execution continues until the line where locations
is defined — at that point, the program needs the results from these asynchronous calls, so you write await
to pause execution until all the locations are fetched.
Here’s how you can think about the differences between these two approaches:
- Call asynchronous functions with
await
when the code on the following lines depends on that function’s result. This creates work that is carried out sequentially. - Call asynchronous functions with
async
-let
when you don’t need the result until later in your code. This creates work that can be carried out in parallel. - Both
await
andasync
-let
allow other code to run while they’re suspended. - In both cases, you mark the possible suspension point with
await
to indicate that execution will pause, if needed, until an asynchronous function has returned.
You can also mix both of these approaches in the same code.
Conclusion
In closing, Swift’s async/await concurrency is a game-changer for handling multiple tasks smoothly in your code. It simplifies the process and keeps things organized. It’s like having a trusty sidekick that helps you juggle tasks effortlessly. So, give it a try and make your coding life a lot easier!
Swift’s async/await, where coding meets concurrency, simplifying the art of multitasking in your programs — one ‘await’ at a time.
Thank you for joining me on the async/await exploration in Swift. Happy coding!