Home iOS & Swift Books Modern Concurrency in Swift

Intermediate async/await & CheckedContinuation Written by Marin Todorov

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

In the previous chapter, you worked through creating custom asynchronous sequences. At this point, you should already feel right at home when it comes to using AsyncSequence and AsyncStream.

You saw that wrapping existing APIs, like Timer and NotificationCenter, is very powerful, letting you reuse your tried-and-tested code in your modern async/await codebase.

In this chapter, you’ll continue working in the same direction. You’ll look into more ways to reuse existing code to the fullest by leveraging Swift’s superpowered concurrency features.

Introducing continuations

Two patterns form the cornerstone of asynchronous programming on Apple platforms: callbacks and the delegate pattern. With completion callbacks, you pass in a closure that executes when the work completes. With the delegate pattern, you create a delegate object, then call certain methods on it when work progresses or completes:

result execution completion closure method(...) other code other code run completion data data data execution delegate delegate owner code call delegate method code call delegate method call delegate method

To encourage the new concurrency model’s adoption, Apple designed a minimal but powerful API that comes in handy when bridging existing code. It centers around the concept of a continuation.

A continuation is an object that tracks a program’s state at a given point. The Swift concurrency model assigns each asynchronous unit of work a continuation instead of creating an entire thread for it. This allows the concurrency model to scale your work more effectively based on the capabilities of the hardware. It creates only as many threads as there are available CPU cores, and it switches between continuations instead of between threads, making it more efficient.

You’re familiar with how an await call works: Your current code suspends execution and hands the thread and system resources over to the central handler, which decides what to do next.

When the awaited function completes, your original code resumes, as long as no higher priority tasks are pending. But how?

When the original code suspends, it creates a continuation that represents the entire captured state at the point of suspension. When it’s time to resume execution or throw, the concurrency system recreates the state from the continuation and the work… well, continues.

hello () async throws -> String { func Task.sleep ( ) try await nanoseconds: 1_000_000 return “Hello world” } try await hello() let result = print (result) suspend resume

This all happens behind the scenes when you use async functions. You can also create continuations yourself, which you can use to extend existing code that uses callbacks or delegates. These APIs can benefit from using await as well.

Manually creating continuations allows you to migrate your existing code gradually to the new concurrency model.

Creating continuations manually

There are two continuation API variants:

Wrapping the delegate pattern

In this chapter, you’ll continue working on the Blabber project, starting where you left off at the end of the last chapter. If you’ve worked through the challenges, just keep up the great work. Otherwise, you can start with this chapter’s starter project, which includes the solved challenge.

ibiqoluil QQCeqocuayBatalax teta rejw xesevutu tiypiy teva lunz gifuyasi soljim nodn kazepati tuxcor zojotoig ezhido danediiz iksura sodoriug usdoni

Managing the authorizations

You’ll get started by creating a location manager and verifying that the user has authorized the app to use the device location data. At this point, users who are running the app for the first time will see the standard system dialogue that asks them to grant authorization:

let location: CLLocation = try await 
withCheckedThrowingContinuation { [weak self] continuation in


SWIFT TASK CONTINUATION MISUSE: shareLocation() leaked its continuation!

Handling the location errors

Open Utility/ChatLocationDelegate.swift, where you’ll find the placeholder type ChatLocationDelegate. Notice that all the CLLocationManagerDelegate requirements are optional, so the file compiles without any of CLLocationManagerDelegate’s methods.

typealias LocationContinuation = CheckedContinuation<CLLocation, Error>
private var continuation: LocationContinuation?
private let manager = CLLocationManager()
init(continuation: LocationContinuation) {
  self.continuation = continuation
  manager.delegate = self
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
  switch manager.authorizationStatus {
  case .notDetermined:
  case .authorizedAlways, .authorizedWhenInUse:
      throwing: "The app isn't authorized to use location data"
    continuation = nil
func locationManager(
  _ manager: CLLocationManager,
  didUpdateLocations locations: [CLLocation]
) {
  guard let location = locations.first else { return }
  continuation?.resume(returning: location)
  continuation = nil
mizhaziukiah = jat qehaloohs: hihosaon = cemifaeml. [ ] tazUgzinoLahedooyr QXCevuxuar ?. lusisoac) riivh roc axfe jaxahz ( : qorcodeepoic bevabu janindazg } veqalaew: = soq zbb idaem DPXobiwoem vuqcMxihvirSlgeyifjYalkeyoifuez { hadzuqaabeux , vemw yozopiarSexaquq ( _ rebodef: LDWekizaubPoyowap meyrm fobogu { { } ])

func locationManager(
  _ manager: CLLocationManager, 
  didFailWithError error: Error
) {  
  continuation?.resume(throwing: error)
  continuation = nil
nowewiuh: = vab rvk ovoax LLMawumuam basvHfoglazLmforartSapxesuuleoc { qimdubaotuaw puldigeuviod = lem iszug: ) { dokSeabJewtOzjak Uxpaz , nekv gafonaawCiyuhej bosufax: JLRigibuapCoyiged ?. ( : ovwiv) mursiloomaag gibipi jtfigelf } _ ( xgdex

edohozox xesu yuqyengq zabakap ofuvabuy pegu VrorWudakeefKemukuzo
mec liuk tujr aywur NcoqJagehiofBabifoka
soy ujkiju wuzefaojr DHQotubuapRisuluk kujniloemoeh.namoku(...)

Using your delegate

Inside the closure of withCheckedThrowingContinuation(_:), insert the following:

self?.delegate = ChatLocationDelegate(continuation: continuation)

<+19.01761470,+72.85616440> +/- 5.00m (speed -1.00 mps / course -1.00) ...

Wrapping callback APIs with continuation

In the system frameworks that Apple introduced after iOS 4, most asynchronous APIs are callback-based.

  .requestAuthorization { result in

Creating the closure

Open BlabberModel.swift and scroll back to the method called shareLocation(), where you added your delegate wrapping code.

let address: String = try await 
withCheckedThrowingContinuation { continuation in
AddressEncoder.addressFor(location: location) { address, error in

switch (address, error) {
case (nil, let error?):
  continuation.resume(throwing: error)
case (let address?, nil):
  continuation.resume(returning: address)
case (nil, nil):
  continuation.resume(throwing: "Address encoding failed")
case let (address?, error?):
  continuation.resume(returning: address)
try await say("📍 \(address)")


Challenge: Build a command-line version of Blabber

This is an optional challenge that you can try on your own to exercise some of the concepts of the last few chapters.

Key points

  • You bridge older asynchronous design patterns to async/await by using CheckedContinuation or its unsafe counterpart, UnsafeCheckedContinuation.
  • For each of your code paths, you need to call one of the continuation’s resume(...) methods exactly once to either return a value or throw an error.
  • You get a continuation by calling either withCheckedContinuation(_:) or withCheckedThrowingContinuation(_:).

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

© 2022 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.