Mastering Continuations in Swift: A Comprehensive Guide

AsyncLearn
3 min readSep 28, 2023

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 the Continuation is being used. This allows identifying the Continuation 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 the Continuation.
  • 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 a Result<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(_:) and UnsafeThrowingContinuation(_:): These are alternatives to withCheckedThrowingContinuation(_:) and withCheckedContinuation(_:) 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 that resume is not called more than once.
  • withCheckedThrowingContinuation(_:): This is similar to withCheckedContinuation(_:) 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 call resume 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 the Continuation 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 use CheckedContinuation.

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/

--

--

AsyncLearn

Stay up-to-date in the world of mobile applications with our specialised blog.