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…

Swift’s Async/Await: Conquer Concurrency

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:

  1. let userLocation = await getUserLocation() In this line, we declare a constant variable userLocation. It's assigned the result of the getLocation() function call, which is marked with await. The await keyword indicates that this is an asynchronous operation, and it will pause execution until the getUserLocation() function completes. It's similar to saying, "Hey, we're waiting for the user's location."
  2. let weatherData = await getWeatherData(for: userLocation): Here, we declare another constant variable weatherData. It's assigned the result of the getWeatherData(for: userLocation) function call. Again, the await 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.
  3. let temperature = await getCurrentTemperature(from: weatherData): In this line, we declare a constant variable temperature. It's assigned the result of the getCurrentTemperature(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.
  4. displayTemperature(temperature): Finally, we call the displayTemperature() function, passing the temperature as an argument. This line displays the temperature to the user, assuming that temperature 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 and async-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!