Chapter 5: Alternatives to storing ChatLocationDelegate in instance variable?

Hi, I’m wondering why in Chapter 5, the ChatLocationDelegate is stored in an instance variable of BlabberModel. It seems like not really the right scoping to me, since shareLocation() could be called multiple times on the same instance, and they would share the same instance variable. Meaning, an older delegate could go out of scope before its continuation was resumed if the variable is reassigned to a new delegate instance.

Initially I thought “well, why not just put it in the function? That seems like the right scope.” And it does work, and seems better, except that then you get a warning about writing to delegate but never using it. My solution to that: delete the ChatLocationDelegate’s init method, and move it all to a start(with:) method that takes the continuation. Then you can initialize the delegate before the continuation block, and just assign the continuation inside the block. I’m curious why something like this wasn’t used in the book, since it’s almost the same interface, but I think better memory semantics / safer.

I did also go a couple steps further, in what I think makes a kind of nicer API (though arguably AsyncStream should be used for this instead), though perhaps not the best for the context of a tutorial like this. I moved the withCheckedThrowingContinuation call to inside ChatLocationDelegate, so that the delegate just exposes an async interface. It looks like this:

class ChatLocationDelegate: NSObject, CLLocationManagerDelegate {
  typealias LocationContinuation = CheckedContinuation<CLLocation, Error>

  private var continuation: LocationContinuation?
  private var manager: CLLocationManager?

  func getLocation() async throws -> CLLocation {
    return try await withCheckedThrowingContinuation({ continuation in
      self.continuation = continuation
      self.manager = CLLocationManager()
      manager?.delegate = self
      manager?.requestWhenInUseAuthorization()
    })
  }

  ...
}

And then to hide the delegate entirely, I added:

extension CLLocationManager {
  class func getLocation() async throws -> CLLocation {
    let delegate = ChatLocationDelegate()
    return try await delegate.getLocation()
  }
}

So then the call site just looks like: let location = try await CLLocationManager.getLocation().

Would love to hear thoughts on this! My coworkers and I have been enjoying the book, and discussing each chapter as we go through, one per week.

1 Like

I’m really glad you and your colleagues find the book useful! I have no doubt that some of the code could be redesigned in a much nicer way when you’re using it in your day-to-day. For my books though I have certain approach, have a look at the completed code:

image

My goal is to 1) emphasize on the linearity of the final piece - clearly show how the steps in this funcction are async but executing serially in the new async/await code and 2) that the async/await code works exactly the same for a delegate and for an async closure. So when you’ve worked through the chapter and you see the completed function, these are the two main takeaways I’d like to leave to the reader.

The API themselves and the exact code design — you can always come back, revisit them, consult with Apple’s own documentation, and as you did — write them better on your own :slight_smile:

Thank you for sharing your code. I also don’t reaaaaaly like that the delegate is stored in an instance property, might be something to improve in the next edition

1 Like