Apple enormously improved the best way to write asynchronous code in Swift with the introduction of Swift Concurrency and the async/await API. Additionally they launched the Continuation API, and you should use this rather than delegates and completion callbacks. You possibly can enormously streamline your code by mastering and utilizing this API.
You’ll study all concerning the Continuation API on this tutorial. Particularly, you’ll replace the tutorial app WhatsApp to make use of the Continuation API as an alternative of legacy patterns. You’ll study the next alongside the best way:
- What the Continuation API is and the way it works
- Easy methods to wrap a delegate-based API part and supply an async interface for it
- Easy methods to present an async API through an extension for parts that use completion callbacks
- Easy methods to use the async API rather than legacy patterns
Getting Began
Obtain the starter undertaking by clicking the Obtain Supplies button on the prime or backside of this tutorial.
Open WhatsThat from the starter folder, and construct and run.

WhatsThat is an image-classifier app. You choose a picture, and it offers a picture description in return.

Right here above is Zohar, beloved Brittany Spaniel — in response to the classifier mannequin :]
The app makes use of one of many normal CoreML neural fashions to find out the picture’s principal topic. Nevertheless, the mannequin’s dedication may very well be incorrect, so it additionally offers a detection accuracy proportion. The upper the share, the extra doubtless the mannequin believes its prediction is correct.
You possibly can both use the default pictures, or you possibly can drag-and-drop your individual pictures into the simulator’s Pictures app. Both manner, you’ll see the accessible pictures in WhatsThat’s picture picker.
Check out the undertaking file hierarchy, and also you’ll discover these core recordsdata:
-
AppMain.swiftlaunches the SwiftUI interface. -
Displayis a bunch containing three SwiftUI views. -
ContentView.swiftaccommodates the primary app display screen. -
ImageView.swiftdefines the picture view utilized in the primary display screen. -
ImagePickerView.swiftis a SwiftUI wrapper round a UIKitUIImagePickerController.
The Continuation API
As a quick refresher, Swift Concurrency means that you can add async to a way signature and name await to deal with asynchronous code. For instance, you possibly can write an asynchronous networking technique like this:
// 1
non-public func fetchData(url: URL) async throws -> Knowledge {
// 2
let (knowledge, response) = attempt await URLSession.shared.knowledge(from: url)
// 3
guard let response = response as? HTTPURLResponse, response.isOk else {
throw URLError(.badServerResponse)
}
return knowledge
}
Right here’s how this works:
- You point out this technique makes use of the async/await API by declaring
asyncon its signature. - The
awaitinstruction is named a “suspension level.” Right here, you inform the system to droop the strategy whenawaitis encountered and start downloading knowledge on a special thread.
Swift shops the state of the present perform in a heap, making a “continuation.” Right here, as soon as URLSession finishes downloading the information, the continuation is resumed, and the execution continues from the place it was stopped.
response and return a Knowledge sort as promised by the strategy signature.When working with async/await, the system mechanically manages continuations for you. As a result of Swift, and UIKit particularly, closely use delegates and completion callbacks, Apple launched the Continuation API that will help you transition current code utilizing an async interface. Let’s go over how this works intimately.
Suspending The Execution
SE-0300: Continuations for interfacing async duties with synchronous code defines 4 completely different features to droop the execution and create a continuation.
withCheckedContinuation(_:)withCheckedThrowingContinuation(_:)withUnsafeContinuation(_:)withUnsafeThrowingContinuation(_:)
As you possibly can see, the framework offers two variants of APIs of the identical features.
-
with*Continuationoffers a non-throwing context continuation -
with*ThrowingContinuationadditionally permits throwing exceptions within the continuations
The distinction between Checked and Unsafe lies in how the API verifies correct use of the resume perform. You’ll find out about this later, so preserve studying… ;]
Resuming The Execution
To renew the execution, you’re speculated to name the continuation supplied by the perform above as soon as, and solely as soon as, by utilizing one of many following continuation features:
-
resume()resumes the execution with out returning a consequence, e.g. for an async perform returningVoid. -
resume(returning:)resumes the execution returning the desired argument. -
resume(throwing:)resumes the execution throwing an exception and is used forThrowingContinuationsolely. -
resume(with:)resumes the execution passing aConsequenceobject.
Okay, that’s sufficient for concept! Let’s leap proper into utilizing the Continuation API.
Changing Delegate-Based mostly APIs with Continuation
You’ll first wrap a delegate-based API and supply an async interface for it.
Take a look at the UIImagePickerController part from Apple. To deal with the asynchronicity of the interface, you set a delegate, current the picture picker after which await the consumer to select a picture or cancel. When the consumer selects a picture, the framework informs the app through its delegate callback.

Though Apple now offers the PhotosPickerUI SwiftUI part, offering an async interface to UIImagePickerController continues to be related. For instance, you might must assist an older iOS or might have personalized the move with a particular picker design you need to keep.
The thought is so as to add a wrapper object that implements the UIImagePickerController delegate interface on one facet and presents the async API to exterior callers.

Good day Picture Picker Service
Add a brand new file to the Providers group and title it ImagePickerService.swift.
Substitute the content material of ImagePickerService.swift with this:
import OSLog
import UIKit.UIImage
class ImagePickerService: NSObject {
non-public var continuation: CheckedContinuation<UIImage?, By no means>?
func pickImage() async -> UIImage? {
// 1
return await withCheckedContinuation { continuation in
if self.continuation == nil {
// 2
self.continuation = continuation
}
}
}
}
// MARK: - Picture Picker Delegate
extension ImagePickerService: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo information: [UIImagePickerController.InfoKey: Any]
) {
Logger.principal.debug("Consumer picked photograph")
// 3
continuation?.resume(returning: information[.originalImage] as? UIImage)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
Logger.principal.debug("Consumer canceled selecting up photograph")
// 4
continuation?.resume(returning: UIImage())
}
}
First, you’ll discover the pickImage() perform is async as a result of it wants to attend for customers to pick out a picture, and as soon as they do, return it.
Subsequent are these 4 factors of curiosity:
- On hitting
withCheckedContinuationthe execution is suspended, and a continuation is created and handed to the completion handler. On this situation, you employ the non-throwing variant as a result of the async performpickImage()isn’t throwing. - The
continuationis saved within the class so you possibly can resume it later, as soon as the delegate returns. - Then, as soon as the consumer selects a picture, the
resumeis known as, passing the picture as argument. - If the consumer cancels selecting a picture, you come an empty picture — a minimum of for now.
As soon as the execution is resumed, the picture returned from the continuation is returned to the caller of the pickImage() perform.
Utilizing Picture Picker Service
Open ContentViewModel.swift, and modify it as follows:
- Take away the inheritance from
NSObjecton theContentViewModeldeclaration. This isn’t required now thatImagePickerServiceimplementsUIImagePickerControllerDelegate. - Delete the corresponding extension implementing
UIImagePickerControllerDelegateandUINavigationControllerDelegatefeatures, yow will discover it underneath// MARK: - Picture Picker Delegate. Once more, these aren't required anymore for a similar purpose.
Then, add a property for the brand new service named imagePickerService underneath your noImageCaption and imageClassifierService variables. You may find yourself with these three variables within the prime of ContentViewModel:
non-public static let noImageCaption = "Choose a picture to categorise"
non-public lazy var imageClassifierService = attempt? ImageClassifierService()
lazy var imagePickerService = ImagePickerService()
Lastly, exchange the earlier implementation of pickImage() with this one:
@MainActor
func pickImage() {
presentImagePicker = true
Activity(precedence: .userInitiated) {
let picture = await imagePickerService.pickImage()
presentImagePicker = false
if let picture {
self.picture = picture
classifyImage(picture)
}
}
}
As pickImage() is a synchronous perform, you need to use a Activity to wrap the asynchronous content material. Since you’re coping with UI right here, you create the duty with a userInitiated precedence.
The @MainActor attribute can be required since you’re updating the UI, self.picture right here.
After all of the modifications, your ContentViewModel ought to seem like this:
class ContentViewModel: ObservableObject {
non-public static let noImageCaption = "Choose a picture to categorise"
non-public lazy var imageClassifierService = attempt? ImageClassifierService()
lazy var imagePickerService = ImagePickerService()
@Printed var presentImagePicker = false
@Printed non-public(set) var picture: UIImage?
@Printed non-public(set) var caption = noImageCaption
@MainActor
func pickImage() {
presentImagePicker = true
Activity(precedence: .userInitiated) {
let picture = await imagePickerService.pickImage()
presentImagePicker = false
if let picture {
self.picture = picture
classifyImage(picture)
}
}
}
non-public func classifyImage(_ picture: UIImage) {
caption = "Classifying..."
guard let imageClassifierService else {
Logger.principal.error("Picture classification service lacking!")
caption = "Error initializing Neural Mannequin"
return
}
DispatchQueue.world(qos: .userInteractive).async {
imageClassifierService.classifyImage(picture) { end in
let caption: String
change consequence {
case .success(let classification):
let description = classification.description
Logger.principal.debug("Picture classification consequence: (description)")
caption = description
case .failure(let error):
Logger.principal.error(
"Picture classification failed with: (error.localizedDescription)"
)
caption = "Picture classification error"
}
DispatchQueue.principal.async {
self.caption = caption
}
}
}
}
}
Lastly, it’s good to change the UIImagePickerController‘s delegate in ContentView.swift to level to the brand new delegate.
To take action, exchange the .sheet with this:
.sheet(isPresented: $contentViewModel.presentImagePicker) {
ImagePickerView(delegate: contentViewModel.imagePickerService)
}
Construct and run. It’s best to see the picture picker working as earlier than, however it now makes use of a contemporary syntax that is simpler to learn.
Continuation Checks
Sadly, there may be an error within the code above!
Open the Xcode Debug pane window and run the app.
Now, choose a picture, and you must see the corresponding classification. If you faucet Choose Picture once more to select one other picture, Xcode offers the next error:

Swift prints this error as a result of the app is reusing a continuation already used for the primary picture, and the usual explicitly forbids this! Keep in mind, you need to use a continuation as soon as, and solely as soon as.
When utilizing the Checked continuation, the compiler provides code to implement this rule. When utilizing the Unsafe APIs and also you name the resume greater than as soon as, nevertheless, the app will crash! In the event you overlook to name it in any respect, the perform by no means resumes.
Though there should not be a noticeable overhead when utilizing the Checked API, it is well worth the worth for the added security. As a default, favor to make use of the Checked API. If you wish to do away with the runtime checks, use the Checked continuation throughout growth after which change to the Unsafe when delivery the app.
Open ImagePickerService.swift, and you will see the pickImage now appears to be like like this:
func pickImage() async -> UIImage? {
return await withCheckedContinuation { continuation in
if self.continuation == nil {
self.continuation = continuation
}
}
}
You have to make two modifications to repair the error herein.
First, at all times assign the handed continuation, so it’s good to take away the if assertion, ensuing on this:
func pickImage() async -> UIImage? {
await withCheckedContinuation { continuation in
self.continuation = continuation
}
}
Second, set the set the continuation to nil after utilizing it:
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo information: [UIImagePickerController.InfoKey: Any]
) {
Logger.principal.debug("Consumer picked photograph")
continuation?.resume(returning: information[.originalImage] as? UIImage)
// Reset continuation to nil
continuation = nil
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
Logger.principal.debug("Consumer canceled selecting up photograph")
continuation?.resume(returning: UIImage())
// Reset continuation to nil
continuation = nil
}
Construct and run and confirm that you may choose as many pictures as you want with out hitting any continuation-leak error.
Changing Callback-Based mostly APIs with Continuation
Time to maneuver on and modernize the remaining a part of ContentViewModel by changing the completion handler within the classifyImage(:) perform with a sleeker async name.
As you probably did for refactoring UIImagePickerController, you will create a wrapper part that wraps the ImageClassifierService and exposes an async API to ContentViewModel.
On this case, although, you may also prolong the ImageClassifier itself with an async extension.
Open ImageClassifierService.swift and add the next code on the finish:
// MARK: - Async/Await API
extension ImageClassifierService {
func classifyImage(_ picture: UIImage) async throws -> ImageClassifierService.Classification {
// 1
return attempt await withCheckedThrowingContinuation { continuation in
// 2
classifyImage(picture) { end in
// 3
if case let .success(classification) = consequence {
continuation.resume(returning: classification)
return
}
}
}
}
}
Here is a rundown of the code:
- As within the earlier case, the system blocks the execution on hitting the
await withCheckedThrowingContinuation. - You need not retailer the continuation as within the earlier case since you’ll use it within the completion handler. Simply name the outdated callback-based API and await the consequence.
- As soon as the part invokes the completion callback, you name
continuation.resume<(returning:)passing again the classification obtained.
Including an extension to the outdated interface permits use of the 2 APIs concurrently. For instance, you can begin writing new code utilizing the async/await API with out having to rewrite current code that also makes use of the completion callback API.
You employ a Throwing continuation to mirror that the ImageClassifierService can throw an exception if one thing goes improper.
Utilizing Async ClassifyImage
Now that ImageClassifierService helps async/await, it is time to exchange the outdated implementation and simplify the code. Open ContentViewModel.swift and alter the classifyImage(_:) perform to this:
@MainActor
non-public func classifyImage(_ picture: UIImage) async {
guard let imageClassifierService else {
Logger.principal.error("Picture classification service lacking!")
caption = "Error initializing Neural Mannequin"
return
}
do {
// 1
let classification = attempt await imageClassifierService.classifyImage(picture)
// 2
let classificationDescription = classification.description
Logger.principal.debug(
"Picture classification consequence: (classificationDescription)"
)
// 3
caption = classificationDescription
} catch let error {
Logger.principal.error(
"Picture classification failed with: (error.localizedDescription)"
)
caption = "Picture classification error"
}
}
Here is what is going on on:
- You now name the
ImageClassifierService.classifyImage(_:)perform asynchronously, that means the execution will pause till the mannequin has analyzed the picture. - As soon as that occurs, the perform will resume utilizing the continuation to the code under the
await. - When you have got a classification, you should use that to replace
captionwith the classification consequence.
Be aware: In an actual app, you’d additionally need to intercept any throwing exceptions at this degree and replace the picture caption with an error message if the classification fails.
There’s one closing change earlier than you are prepared to check the brand new code. Since classifyImage(_:) is now an async perform, it’s good to name it utilizing await.
Nonetheless in ContentViewModel.swift, within the pickImage perform, add the await key phrase earlier than calling the classifyImage(_:) perform.
@MainActor
func pickImage() {
presentImagePicker = true
Activity(precedence: .userInitiated) {
let picture = await imagePickerService.pickImage()
presentImagePicker = false
if let picture {
self.picture = picture
await classifyImage(picture)
}
}
}
Since you’re already in a Activity context, you possibly can name the async perform straight.
Now construct and run, attempt selecting a picture another time, and confirm that all the things works as earlier than.
Dealing With Continuation Checks … Once more?
You are nearly there, however just a few issues stay to care for. :]
Open the Xcode debug space to see the app’s logs, run and faucet Choose Picture; this time, nevertheless, faucet Cancel and see what occurs within the logs window.

Continuation checks? Once more? Did not you repair this already?
Effectively, that was a special situation. Here is what’s taking place this time.
When you faucet Cancel, ImagePickerService returns an empty UIImage, which causes CoreML to throw an exception, not managed in ImageClassificationService.
Opposite to the earlier case, this continuation’s resume isn’t known as, and the code due to this fact by no means returns.
To repair this, head again to the ImageClassifierService and modify the async wrapper to handle the case the place the mannequin throws an exception. To take action, you need to verify whether or not the outcomes returned within the completion handler are legitimate.
Open the ImageClassifierService.swift file and exchange the prevailing code of your async throwing classifyImage(_:) (the one within the extension) with this:
func classifyImage(_ picture: UIImage) async throws -> ImageClassifierService.Classification {
return attempt await withCheckedThrowingContinuation { continuation in
classifyImage(picture) { end in
change consequence {
case .success(let classification):
continuation.resume(returning: classification)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
Right here you employ the extra continuation technique resume(throwing:) that throws an exception within the calling technique, passing the desired error.
As a result of the case of returning a Consequence sort is widespread, Swift additionally offers a devoted, extra compact instruction, resume(with:) permitting you to cut back what’s detailed above to this as an alternative:
func classifyImage(_ picture: UIImage) async throws -> ImageClassifierService.Classification {
return attempt await withCheckedThrowingContinuation { continuation in
classifyImage(picture) { end in
continuation.resume(with: consequence)
}
}
}
Gotta find it irresistible! Now, construct and run and retry the move the place the consumer cancels selecting a picture. This time, no warnings can be within the console.
One Ultimate Repair
Though the warning about missing continuation is gone, some UI weirdness stays. Run the app, choose a picture, then attempt selecting one other one and faucet Cancel on this second picture.
As you see, the earlier picture is deleted, whilst you would possibly favor to keep up it if the consumer already chosen one.
The ultimate repair consists of fixing the ImagePickerService imagePickerControllerDidCancel(:) delegate technique to return nil as an alternative of an empty picture.
Open the file ImagePickerService.swift and make the next change.
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
Logger.principal.debug("Consumer canceled selecting a picture")
continuation?.resume(returning: nil)
continuation = nil
}
With this final modification, if the consumer cancels selecting up a picture, the pickImage() perform of ImagePickerService returns nil, that means ContentViewModel will skip setting the picture and calling classifyImage(_:) in any respect.
Construct and run one final time and confirm the bug is gone.
The place to Go From Right here?
Effectively finished! You streamlined your code and now have a constant code model in ContentViewModel.
You began with a ContentViewModel that contained completely different code kinds and needed to conform to NSObject on account of delegate necessities. Little by little, you refactored this to have a contemporary and easier-to-follow implementation utilizing the async/await Continuation API.
Particularly, you:
- Changed the delegate-based part with an object that wraps the delegate and exposes an async perform.
- Made an async extension for completion handler-based part to permit a gradual rewrite of current elements of the app.
- Discovered the variations between utilizing
CheckedandUnsafecontinuations and the best way to deal with the corresponding verify errors. - Have been launched to the forms of continuation features, together with async and async throwing.
- Lastly, you noticed the best way to resume the execution utilizing the
resumedirections and return a worth from a continuation context.
It was a enjoyable run, but as at all times, that is only the start of the journey. :]
To study extra concerning the Continuation API and the small print of the Swift Concurrency APIs, have a look at the Trendy Concurrency in Swift e book.
You possibly can obtain the whole undertaking utilizing the Obtain Supplies button on the prime or backside of this tutorial.
We hope you loved this tutorial. You probably have any questions, ideas, feedback or suggestions, please be part of the discussion board dialogue under!
