When working with asynchronous code we often leverage the use of callbacks so we can execute code when the asynchronous operation finishes. This works fine in simple scenarios but gets complicated if we have to perform a future request based on the result of the previous request. The callback pattern also open doors for not remembering to execute the user interface code on the main thread, which can lead to performance issues.
Swift 5.5 includes a new way of performing asynchronous actions. This is done by the support of async/await feature. In this article we will look at how you can get started with using async/await in your iOS applications.
Invoking Movies API Using Callbacks
Before jumping into the async/await details, let’s take a look at how we currently fetch data from an API using callbacks. For this example we will be using the OMDB API to fetch a list of movies. The code below shows the getMovies function inside the Webservice class, which is responsible for making a network call and fetching all the movies from the OMDB API.
The Result enum allows us to classify the response as success or failure. There are couple of things worth noting about the above code. First, we need to make sure that we access the completion on the main thread as we need to update the user interface. We also need to make sure to return from each of the guard statements. And finally, if we had to make another call which is dependent on the first call then it will become more complicated as we will have to nest the second call within the success handler of the first call.
Now, let’s try to implement the same network call using the async/await approach.
The first thing you will notice is the async keyword right after the name of the function. The async keyword indicates that this function is asynchronous and it will be suspended when called. Suspended does not mean that it will block the thread, but it means that it will let go of the thread so someone else can use it. Once the network call finishes, it will resume itself again and continue.
In iOS 15 and Xcode 13 Apple has exposed several APIs which supports async. This includes URLSession, HealthKit etc. As you can see we are using the new URLSession.shared.data function which supports async and hence we can await for the result.
Please keep in mind that you cannot use await unless your function is async
The async/await approach is definitely more concise and readable. If later we have to perform a second request to another url then we can simply continue with our code without any nesting.
Calling Asynchronous Function
If you are following MVVM design pattern then your view model will be responsible for calling the Webservice().getMovies functions and returning the result to the view to be displayed. In the implementation below our view model MovieListViewModel exposes the fetchAllMovies function, which calls the getMovies of Webservice class.
Since getMovies function in Webservice is an async function and can throw, the caller needs to use try await when calling it. Now the view can use the MovieListViewModel and get all the movies.
The task modifier has async support and that is why we can await inside the modifier. If you are in a situation, where you want to await on an async function but you are not in async scope then you can use async closures as shown below:
The async closure returns you a handler, which you can use to cancel the task.
Implementing Custom Async Functions
In the above code we utilized the URLSession async/await behavior, but what if we wanted to implement async/await in our own functions.
Let’s consider if we have a getStocks function as shown below:
The current implementation of getStocks uses the callbacks to send an array of stocks back to the caller. We can update this function to access it through async/await. This is shown in the implementation below:
We implemented another getStocks function, which is marked with async. Then we used withUnsafeContinuation, which is kind of like a Promise. The withUnsafeContinuation closure calls the getStocks and when the getStocks returns the data, it resumes the operation. The calling code in the StockListViewModel is shown below:
The getAllStocks in StockListViewModel is not decorated with async, since we are using the async closure approach. Another thing to notice is the @MainActor attribute on the StockListViewModel class. The @MainActor allows our properties like stocks to be set on the main thread, rather than the background thread.
Async and await is a welcome feature in Swift language and it will allow developers to write clear and readable code. Hopefully, we will see a better and more intuitive implementation of withUnsafeContinuation in the future.
If you liked this article and want to support my work then check out my courses on Udemy.