Mastering Continuations in Swift: A Comprehensive Guide
A Continuation
is a mechanism that allows us to bridge synchronous and asynchronous code.
func withCheckedContinuation<T>(
function: String = #function,
_ body: (CheckedContinuation<T, Never>) -> Void
) async -> T
This is the signature of withCheckedContinuation(function:_:)
. Let's look at the parameters it requires:
function
is used to identify the function where theContinuation
is being used. This allows identifying theContinuation
for runtime analysis.body
parameter accepts a closure of type(CheckedContinuation<T, Never>) -> Void
. This will be the block of code we execute synchronously as part of theContinuation
.- Note that this function is generic and can return any type.
How to Use Continuation?
One use of Continuation
is to convert code that uses completion handlers into code compatible with async/await.
Here’s an example of how we can use this tool:
func decodeData(_ encodedData: [EncodedInfo],
completion: @escaping ([Info]) -> Void) {
// Perform processing
// Then return an array of Info objects using the completion handler.
}
This function, after processing, returns the result using a completion handler.
To use this function with async/await
, we use the following code:
func decodeData(_ encodedData: [EncodedInfo]) async -> [Info] {
await withCheckedContinuation { continuation in
decodeData(encodedData) { decodedInfo in
continuation.resume(returning: decodedInfo)
}
}
}
We wrap the code that uses completion handlers within a withCheckedContinuation
and call the function:
decodeData(_ encodedData: [EncodedInfo],
completion: @escaping ([Info]) -> Void)
Finally, we use the continuation
to resume the task and return decodedInfo
:
continuation.resume(returning: decodedInfo)
Ways to Resume a Continuation
Depending on the type of Continuation
you are using, you can use the following methods to resume tasks:
resume(returning:)
is used in the example and resumes the task by returning a value.resume(throwing:)
resumes the task by throwing an exception from the suspension point.resume(with:)
resumes the task by returning aResult<T, E>
, meaning it can return a value or an error.resume()
resumes the task without returning anything.
Other Available Continuations
There are other types of Continuation
available:
withUnsafeContinuation(_:)
andUnsafeThrowingContinuation(_:)
: These are alternatives towithCheckedThrowingContinuation(_:)
andwithCheckedContinuation(_:)
that perform no runtime checking or diagnostics. We must be very careful when using Unsafe APIs because it is our responsibility to ensure that the code has no issues. For example, ensuring thatresume
is not called more than once.withCheckedThrowingContinuation(_:)
: This is similar towithCheckedContinuation(_:)
but can throw exceptions.
Considerations When Using Continuations
Here are some important considerations when using Continuations:
- The code passed in
body
runs synchronously within the task from which it is called. Once the task returns, it gets suspended. Therefore, ensure that you callresume
using one of the available methods. - You should only call one of the
resume
methods once within an execution. - Failing to call
resume
may result in memory leaks, and the task from which theContinuation
was created will remain suspended indefinitely. - Attempting to resume a
Continuation
more than once will cause a runtime error. - Unless you are sure that performance is significantly improved by using
UnsafeContinuation
, it is advisable to useCheckedContinuation
.
If you want to read the Spanish version of this article, you can find it here: https://asynclearn.com/blog/mezclando-codigo-sincrono-y-asincronico-usando-checkedcontinuation/