Translating closures to async
In this post, we will discuss how to translate closures to async in Swift.
What is continuation
?
In Swift, a continuation is a construct that allows you to suspend and resume execution of a piece of code at a later time. It's often used in the context of asynchronous programming.
When you're dealing with asynchronous tasks, you often need to wait for some operation to complete before you can continue with the rest of your code. This is where continuation comes in.
You can think of a continuation as a placeholder for the future result of an asynchronous operation. When the operation is complete, you can "resume" the continuation with the result, allowing the rest of your code to proceed.
In Swift 5.5 and later, continuation is often used in the implementation of async/await patterns. For example, you might use a continuation to wrap a callback-based asynchronous API into a function that can be used with await.
Use Case: continuation
Imagine you have a function that takes a callback as an argument, and you want to convert it to an async function. Here's an example of what that might look like:
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
// Perform some asynchronous operation
// ...
// When the operation is complete, call the completion handler
completion(.success(data))
}
This function takes a completion handler as an argument, and when the asynchronous operation is complete, it calls the completion handler with the result.
To convert this function to an async function, you can use a continuation. Here's how you might do that:
func fetchData() async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
fetchData { result in
switch result {
case .success(let data):
continuation.resume(returning: data)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
Example 2:
Imagine we have a function whihout Result
type, and we want to convert it to an async function. Here's an example of what that might look like:
func fetchData(completion: @escaping (Data?, Error?) -> Void) {
// Perform some asynchronous operation
// ...
// When the operation is complete, call the completion handler
completion(data, error)
}
To convert this function to an async function, you can use a continuation. Here's how you might do that:
func fetchData() async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
fetchData { data, error in
if let data = data {
continuation.resume(returning: data)
} else if let error = error {
continuation.resume(throwing: error)
}
}
}
}
withCheckedThrowingContinuation
vs withUnsafeContinuation
withCheckedContinuation
will add runtime checks to detect any improper use of the continuation and warn you if it were to happen. withUnsafeContinuation
will perform none of these checks.
Caveats
continuation
must be called exactly once.
Wrap up
Continuations are a powerful tool for working with asynchronous code in Swift. They allow you to suspend and resume execution of a piece of code at a later time, making it easier to work with asynchronous operations.
Resources: https://developer.apple.com/documentation/swift/checkedcontinuation
https://developer.apple.com/documentation/swift/withcheckedcontinuation(function:_:)
Read more
- Understanding assertions in Swift • 4 minutes reading time.
- Simplifying multiplatform Colors • 15 minutes reading time.
- A Guide to UI Testing in Swift • 15 minutes reading time.
Share
Share Mastodon Twitter LinkedIn Facebook