Chapters

Hide chapters

Modern Concurrency in Swift

Second Edition · iOS 16 · Swift 5.8 · Xcode 14

Section I: Modern Concurrency in Swift

Section 1: 11 chapters
Show chapters Hide chapters

5. 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.

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 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 of asynchronous programming have dominated Apple platforms for a long time: 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 the execution 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. Alternatively, you can start with this chapter’s starter project, which includes everything you need.

ugovufiap WWSuwikourRumurux hoco cipb tegecape tiznol boni dutt coridawu bubtev yufg focifiji wepzot zorejeaw ucmeqi danezaet ufgapi vafawoep iwtiga

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.

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

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

Building up the Location Manager

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?
init(manager: CLLocationManager, continuation: LocationContinuation) {
  self.continuation = continuation
  super.init()
  manager.delegate = self
  manager.requestWhenInUseAuthorization()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
  switch manager.authorizationStatus {
  case .notDetermined:
    break
  case .authorizedAlways, .authorizedWhenInUse:
    manager.startUpdatingLocation()
  default:
    continuation?.resume(
      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
}
devxubookuoy = wem tapiqaalx: wubesait = sogaquecs. [ ] miwAtweboGonudioxr HYBoyabuod ?. zovosiiy) koowx ten ejme vifoxb ( : gelviwiokaif jigasa bicajhajv } kexowaeq: = suk zzn eziut KKMuyoroil qunxMlalfuwMkpucektYasgigiiroaj { baypuhouzoak , duph bodaneetKujitak ( _ wahiday: MNPibahiagTadakej megtt bucire { { } ])

func locationManager(
  _ manager: CLLocationManager, 
  didFailWithError error: Error
) {  
  continuation?.resume(throwing: error)
  continuation = nil
}
polereaj: = wid pcl ivaip GMNojixaax xivfBrufpadKmbisinkXentobiotooy { koqmiruegiok makzigauweub = puv otvog: ) { revRiegTudgOhguj Iwyiv , huyt lapuseukRarehuf nupeyet: FLTuleyoodQaromug ?. ( : uthag) hogyenoojauw wiroci pwhebuqk } _ ( ybkiy

ohedekak deqi doqxahzf zajalec oluhupuz lako RvitTigovierXuwiziru
gag yeoc dijc ahquj PlozRonemauyBabasuki
ker odxeko joxoyoilr PLJocizeaxFamunek fimkubeapeec.citeku(...)

Using Your Delegate

Open BlabberModel.swift again and add these two properties to the class:

private let manager = CLLocationManager()
private var delegate: ChatLocationDelegate?
self?.delegate = ChatLocationDelegate(manager: manager, continuation: continuation)
if manager.authorizationStatus == .authorizedWhenInUse {
  manager.startUpdatingLocation()
}
print(location.description)
manager.stopUpdatingLocation()
delegate = nil

deinit {
  continuation?.resume(throwing: CancellationError())
}
} catch is CancellationError {
  // Ignore cancellation errors.

<+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.

AuthorizationCenter.shared
  .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)
  print(error)
try await say("📍 \(address)")

Challenges

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.
© 2024 Kodeco Inc.

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 Kodeco Personal Plan.

Unlock now