ASYNC / AWAIT

Async Await in Swift — Part 1

AmitAswal
5 min readJul 12, 2024

--

In Swift, the async and await keywords provide a way to handle asynchronous operations in a clear and concise manner. This feature, introduced in Swift 5.5, allows you to write asynchronous code that is easier to read and maintain, following a pattern similar to synchronous code.

This is going to be a 3 part series where we will discuss Async / Await in detail with example.

Before that lets understand Async and Sync code.

Asynchronous Code: Asynchronous code allows certain operations in a program to be paused and resumed later, while still ensuring that only one part of the program executes at any given time. This approach is beneficial for handling tasks that may take an unpredictable amount of time, such as fetching data from the internet or reading large files. By suspending and resuming operations, asynchronous code enables the program to continue performing quick tasks like updating the user interface while waiting for longer operations to complete.

Parallel Code: Parallel code involves running multiple pieces of code simultaneously. For example, on a computer with a four-core processor, four different tasks can be executed at the same time, each on a separate core. This approach maximizes the use of available processing power and speeds up the completion of tasks that can be executed concurrently.

Understanding Async and Await in Swift

Async and Await: In Swift, an asynchronous function can release the thread it’s running on, allowing another asynchronous function to use that thread while the first function is paused. When the first function is ready to resume, Swift does not guarantee that it will continue on the same thread.

Why Async / Await ?

Before understanding the Async / Await , we should question ourself why it came.

Let’s discuss how we were achieving async calls before and what were their disadvantages because of which Async / Await came.

Async code could be achieved through below ways before async / await was introduced.

Async code could be achieved through below ways before async / await was introduced.

Before the introduction of async/await in Swift, asynchronous code was typically managed using completion handlers (callbacks), delegates, or reactive programming frameworks like Combine. While these methods were functional, they introduced several challenges:

Issues with previous techniques

Pyramid of Doom:

  • Description: The “Pyramid of Doom” refers to the deep nesting of callbacks or completion handlers when performing sequential asynchronous operations. This pattern makes the code harder to read and maintain.
Pyramid of Doom

Strong Reference Cycle:

  • Description: Strong reference cycles occur when closures capture self strongly, leading to memory leaks. Developers often need to remember to use [weak self] to prevent these cycles.
Strong reference cycle

Error Handling:

  • Description: Handling errors in asynchronous code can be cumbersome, especially when chaining multiple asynchronous operations where each step might fail independently.
Error handling not easy to read / understand

So all these issues were resolved with the introduction of Async / Await.

Lets get down to an example :

To define an asynchronous function, you use the async keyword in its declaration, placed after its parameters:

func fetchData() async -> Data {
// Function body
}

When calling an asynchronous method, the program’s execution pauses until that method completes. This potential suspension is indicated by the await keyword, which must be placed before the call to the asynchronous method. Within an asynchronous function, the execution flow is suspended only when another asynchronous method is called, with each suspension point marked by await:

let data = await fetchData()

So Imagine that we need to download a photo, so we will create a function which does the network request and when the data comes back we call the completion closure to notify the caller like this:

func downloadImage(from url: URL, completion: @escaping (UIImage?) -> ()) {
URLSession.shared.dataTask(with: URLRequest(url: url)) { data, response, error in
if let data = data,
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode >= 200 && httpResponse.statusCode <= 300 {
completion(UIImage(data: data))
} else if error != nil {
completion(nil)
}
}.resume()
}

So async gave us a super sweet away to handle that by writing async to the function and use await to wait to the request to finish and download the photo data.

func downloadImageAsync(from url: URL) async throws-> UIImage? {
do {
let (data, response) = try await URLSession.shared.data(from: url)
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode >= 200 && httpResponse.statusCode <= 300 {
return UIImage(data: data)
}
throw NSError(domain: "Response not corect", code: 100)
} catch {
throw NSError(domain: "Data not corect", code: 101)
}
}
do{
let image = try await downloadImageAsync(from: URL(string: "https://fastly.picsum.photos/id/990/200/300.jpg?hmac=6QkvunJPzSUAgkuY7p_hlJq5SmEdhlV01fbh5cMzKgg")!)
}catch let error{
print(error)
}

NOTE : “throws” is a keyword used with async to tell the compiler that this method can throw error. Error needs to be handled from where the method is called with await keyword. If you do not use the “throws” keyword you can ignore catching the error.

You can download the sample code foe API handler using Async/Await from here

Now how Pyramid of Doom is addressed using Async / Await ?

Lets see this in next part : Part2https://medium.com/@amitaswal87/async-await-inswift-part2-8030bbdc72d8

If you like this post, please share and give claps so others can find it 👏👏

--

--