Async Await in Swift

Ahmed Adam
4 min readJun 27, 2023

--

Async/Await in Swift Explained with Code.

Async programming is crucial in modern software development as it enables applications to execute multiple tasks concurrently. In Swift, managing async code used to be complex, often involving callbacks or completion handlers. However, Swift 5.5 introduced native support for async/await, offering a simpler approach to writing async code. This article explores the concept of async/await in Swift, highlights its benefits, and provides practical code examples to illustrate its usage.

Understanding Async/Await:

To demonstrate the usage of async/await, let’s consider a common scenario where we want to fetch data from a remote API, such as an array of movie objects. The following code snippet showcases a traditional asynchronous function that relies on callbacks:

func fetchMovies(completion: @escaping (Result<[Movie], Error>) -> Void) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
} else if let data = data {
do {
let movies = try JSONDecoder().decode([Movie].self, from: data)
completion(.success(movies))
} catch {
completion(.failure(error))
}
}
}.resume()
}

In this function, we handle potential errors in the response and use JSONDecoder to decode the received data into an array of Movie objects. If the decoding process is successful, we invoke the completion handler with a .success result containing the movies. Otherwise, we pass the encountered error through the completion handler using .failure.

Now, let’s explore how async/await simplifies this code and makes it cleaner and more concise.

What is Async ?

the async keyword is used to define functions or blocks of code as asynchronous. It is part of the async/await paradigm introduced in Swift 5.5.

When a function is marked as async, it means that it can perform potentially long-running operations without blocking the execution of the calling code. Instead of waiting for the function to complete before moving on to the next line of code, the program can continue execution while the asynchronous task is in progress.

func fetchMovies() async throws -> [Movie] {
// Perform asynchronous operations
// ...
}

The provided code snippet declares an async function named fetchMovies() that fetches an array of movies asynchronously. The function has a return type of [Movie], indicating that it will ultimately return an array of Movie objects.

What is Await?

the await keyword is used in conjunction with async functions to suspend the execution of code until an asynchronous task completes. It allows you to wait for the result of an asynchronous operation without blocking the execution of the calling code.

When you encounter an await expression within an async function, the function temporarily pauses its execution and allows other code to run. It effectively defers the execution of the function until the awaited task finishes and the result becomes available.

do {
let movies = try await fetchMovies()
// Handle the fetched movies
// ...
} catch {
// Handle any errors that occurred during the fetch operation
// ...
}

Overall, the provided explanation highlights that the fetchMovies() function is designed to perform asynchronous operations and fetch an array of movie objects, while considering error handling through the throws keyword. The specific implementation details of the asynchronous operations are left out, as they can vary depending on the data source and network frameworks being used.

Adopting async/await:

With the adoption of async/await, the code for the fetchMovies function has indeed become simpler, cleaner, and more readable. Here's the updated version:

   func fetchMovies() async throws -> [Movie] {
guard let url = URL(string: "Your url") else {
throw APIError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
throw APIError.invalidResponse
}
do {
let decorder = JSONDecoder()
let movies = try decorder.decode([Movie].self, from: data)
return movies
} catch{
throw APIError.invalidData
}
}

By using async/await, we eliminate the need for callbacks or completion handlers. The function signature indicates that it is an async function that can throw an error. Inside the function, we use await to wait for the data response from the API using URLSession.shared.data(from:). The response data is then decoded into an array of Movie objects using JSONDecoder. Finally, the function directly returns the movies. Additionally, when handling errors, you can throw a custom error with a custom message to indicate the specific error encountered.

The adoption of async/await improves the readability of the code and makes the asynchronous flow appear more sequential and natural, which leads to cleaner and more maintainable code.

Conclusion:

The introduction of async/await in Swift has greatly simplified asynchronous programming, allowing developers to write cleaner, more readable, and less error-prone code. By using the async and await keywords, along with structured concurrency and error handling, developers can harness the power of concurrency without sacrificing code maintainability. With async/await, Swift has taken a significant step forward in making asynchronous programming more approachable and intuitive.

Remember to leverage the latest version of Swift and embrace async/await to enhance your development workflow and create more efficient and responsive applications.

--

--

Ahmed Adam

Senior iOS Engineer | Swift | SwiftUI | Objective-C | Agile | Scrum | Mobile Development |