Async/await in Swift 5.5
For many of us, asynchronous programming is a regular, day-to-day activity during software development. It basically lets us perform a time consuming tasks in background, without blocking the main thread which consequently allows users to keep using the app without freezing itself until the long-running tasks complete. Although completion handlers are widely being used by developers, we do have new keywords provided by Swift 5.5 called async and await which are capable of easing off the concerns and potential issues that developers could face with closures. This article explains how this new syntax can be used to support concurrency in Swift 5.5.
Concurrency denotes to multiple things happening at the same time.
Completion handlers
A completion handler is basically a function that gets passed as a parameter of another function. It allows us to send back values to the caller even after the function returns, consequently, preventing long running and time consuming functions from blocking execution until it completes. Instead, a completion closure is used to send data back to the caller once ready.
Problems with completion handlers:
- Everyone makes mistakes, so do developers. Sometimes there can be instances where completion handler is getting called multiple times, or not getting called at all.
- In swift, completion handler is always a closure where we cannot ensure that it gets invoked. Sometimes we might just return the compiler even before the completion handler gets called.
- It was harder to send back errors along with completion handlers until the introduction of Result type in Swift 5.0.
- The code can eventually lead to a pyramid of doom as we expect the code to run sequentially, and the latter parts of code can depend on the results of multiple completion handlers.
Before going through the new syntax, let’s look at the code implemented using completion handlers aka closures.
The above code has three sample functions to download image from server, to resize the downloaded image and finally to apply a filter to the resized image. For simplicity, only the core lines of code can be seen and all the code for downloading, resizing and applying filters have been ignored for convenience. A delay has been added right before completion handler gets called for simulation.
Async & await
An asynchronous function or asynchronous method is a kind of function or method that can be suspended while it’s partway through execution. https://docs.swift.org/
Unlike ordinary synchronous functions, async functions are able to pause in the middle when they are waiting for a time-consuming task to complete, allowing the code to make use of the the data returned by the asynchronous task. But first, we should let the compiler know if its an async function and also the places in the code where the compiler should wait for async functions to complete.
In order to indicate whether a function is asynchronous, async keyword is used in the function declaration just after the function parameters.
If the function throws while being async, the syntax would be async throws after function parameters.
Let’s implement the above example using async and await.
Just like the first example, most of the code is ignored for convenience, all three of the above functions have been marked as async in order to indicate that they perform asynchronous tasks and two of the functions have been marked as throws which means those functions can throw an error other than the expected return type which is a UIImage.
Task.sleep(_:)
method does nothing but waits at least the passed number of nanoseconds before the method returns. Since this example is implemented in Xcode 13.0 beta, there is a bug at the moment that crashes when larger values are passed for the duration.
Here, the function is intended to download the image from the url passed as a parameter, perform some asynchronous operations and returns either an error or the processed image to the caller. All three calls have been marked with await which tells the compiler that it needs to pause execution until it receives expected image or an error from async function before continuing with the rest of the code. Two function calls have been marked with try as they can return an error instead of expected return type which is UIImage.
Heres what happens:
- The execution of the function downloadProfileImage(url: String) starts and runs till the first await. Since there is an await in the first line of code itself, function suspends execution and waits for downloadImage(url: String) function to return.
- When the execution of this function is suspended due to await, the processor is able to run some other code in the program until it meets the next await.
- After the image downloads, the function resumes its execution to the next line which is also await that indicates a pause, consequently pausing the execution again and waiting for a return to continue.
- At last, there are two possible returns by this function. It can either throw an error if it fails anything before the last return or if the execution completes without throwing any error, it finally returns the expected UIImage to its caller.
This mechanism of pausing the execution with await is also called yielding the thread as Swift suspends execution of the awaiting code and executes some other code in the program in that thread instead.
Async/await resolves the above mentioned potential problems of completion handlers/closures, moreover, helps reduce the length of the code drastically by allowing the code to be constructed based on ideas.
According to Swift documentation, only certain places in your program can call asynchronous functions or methods:
- Code in the body of an asynchronous function, method, or property.
- Code in the static main() method of a structure, class, or enumeration that’s marked with @main.
- Code in a detached child task.
Please refer the Swift documentation in link below.
Where to go:
This article gives just an introduction on how things have simplified in Swift 5.5 when it comes to concurrency. These syntaxes and many more, give developers a convenient handle of async tasks with a few lines of code. Along with these, there are plenty of things that you can go through by yourself in the Swift document itself.