Logo for tanaschita.com

How to bridge completions handlers to Swift's async/await

Understand Swift continuations to create your own async/await functions.

05 Dec 2022 · 3 min read

Before Apple introduced Swift's new async/await concurrency model at WWDC21, completion callbacks were a common pattern to work with asynchronous code.

With completion callbacks, we pass in a closure that executes when the work completes. When adapting iOS applications to async/await, we need a way to bridge completion handlers to async functions. This is where so called continuations come in.

Sponsorship logo
Preparing for a technical iOS job interview - updated for iOS 17
Check out my book on preparing for a technical iOS job interview with over 200 questions & answers. Test your knowledge on iOS topics such as Swift, SwiftUI, Combine, HTTP Networking, Authentication, SwiftData & Core Data, Concurrency with async/await, Security, Automated Testing, Machine Learning and more.
LEARN MORE

Every time we call await, the current code suspends by creating a continuation that captures the state at the point of suspension. When an awaited function completes, the captured state is recreated from the continuation and the original code resumes. This all happens behind the scenes when we use existing async functions.

To create our own async functions, we can use so called continuation functions. Continuation functions give us control over suspending and resuming a function.

Let's see how to do that.

Using withCheckedContinuation()

Let's jump into a small example and create an async function version from the following completion handler function:

func load(completion: (Int) -> Void) {
...
}

The async version of that function can be created as follows:

func load() async -> Int {
return await withCheckedContinuation({ continuation in
load() { result in
continuation.resume(returning: result)
}
})
}

In the example above, we use Swift's withCheckedContinuation function to suspend and then call resume after we get a result from our completion handler function. Resuming from a continuation must happen exactly once.

Besides withCheckedContinuation(), there are other continuation functions we can use:

Using withCheckedThrowingContinuation()

The withCheckedThrowingContinuation() function behaves the same as withCheckedContinuation(), additionally allowing to handle errors. For example, if our completion handler function completes with a possible error Result<Int, Error>:

func load(completion: (Result<Int, Error>) -> Void) {
}

We could wrap it into an async throws function as follows:

func load() async throws -> Int {
return try await withCheckedThrowingContinuation({ continuation in
load() { result in
switch result {
case .success(let successResult):
continuation.resume(returning: successResult)
case .failure(let error):
continuation.resume(throwing: error)
}
}
})
}

Using withUnsafeContinuation() and withUnsafeThrowingContinuation()

Both functions, withUnsafeContinuation() and withUnsafeThrowingContinuation() behave the same as withCheckedContinuation() and withCheckedThrowingContinuation(). The only difference is that while checked continuations perform runtime checks for missing or multiple resume operations, unsafe continuations don't.

For development, checked continuations are a good choice because they make testing easier providing direct feedback for missing or multiple resume operations. Since both types have the same interface, we can replace them with unsafe continuations later on without making any other changes to reduce the overhead.

Sponsorship logo
Preparing for a technical iOS job interview - updated for iOS 17
Check out my book on preparing for a technical iOS job interview with over 200 questions & answers. Test your knowledge on iOS topics such as Swift, SwiftUI, Combine, HTTP Networking, Authentication, SwiftData & Core Data, Concurrency with async/await, Security, Automated Testing, Machine Learning and more.
LEARN MORE

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

async/await

concurrency

swift

Memory management for async/await and tasks in Swift

Learn about weak self references when working with the async/await API.

03 Oct 2022 · 2 min read

Latest articles and tips

© 2024 tanaschita.com

Privacy policy

Impressum