Chapter 4: Why the need to double up on await for `.notifications(for:)`?

In the body of observeAppStatus we end up with:

    for await _ in await NotificationCenter.default.notifications(for: UIApplication.willResignActiveNotification) {
      try? await say("\(username) went away", isSystemMessage: true)
    }

You can see there are two awaits – seemingly the first for awaiting the elements in the AsyncStream, and the second for awaiting the result of the notifications(for:) function call. The thing is that notifications(for:) is not marked async, but the compiler still requires the second await to compile. This is not the case if we use a nearly-identical example I’ll call stupidGarbage:

extension NotificationCenter {
  // Method from book, not marked async
  func notifications(for name: Notification.Name) -> AsyncStream<Notification> {
    return AsyncStream<Notification> { continuation in
      NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil) { note in
        continuation.yield(note)
      }
    }
  }
  
  // Nearly identical method, also not marked async
  func stupidGarbage() -> AsyncStream<String> {
    return AsyncStream<String> { continuation in
      continuation.finish()
    }
  }
}

In this case, awaiting the elements doesn’t require the second await:

func observeAppStatus() async {
    // Removing the second await causes a compilation error
    for await _ in await NotificationCenter.default.notifications(for: UIApplication.willResignActiveNotification) {
      try? await say("\(username) went away", isSystemMessage: true)
    }
    
    // One await works fine here though
    for await thing in NotificationCenter.default.stupidGarbage() {
      try? await say(thing)
    }
  }

Is there some implicit thing here that I’m missing? Maybe a compiler error?

The notifications(for:) await is due to the fact that UIApplication is annotated with @MainActor, so accessing the UIApplication.willResignActiveNotification notification name needs to cross the actor isolation layer, making it an async operation.

The code can be rewritten as:

    Task {
      for await _ in NotificationCenter.default.notifications(for: await UIApplication.willResignActiveNotification) {
        try? await say("\(username) went away", isSystemMessage: true)
      }
    }

If the notification name is obtained in a sync fashion, the second await is indeed not needed:

      for await _ in NotificationCenter.default.notifications(for: Notification.Name("Test")) {
        try? await say("\(username) went away", isSystemMessage: true)
      }

Thanks for the insight and adding points to explaining the code @ralucat. Great job