← All daysDay 34 of 49 · Part 9: Swift concurrency
Day 34

AsyncSequence and AsyncStream

These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.

Lesson 39

AsyncSequence and AsyncStream

An AsyncSequence delivers values over time. You consume one with for await value in sequence. Many system APIs provide one, such as URLSession.bytes and NotificationCenter.notifications. AsyncStream lets you build your own from code that uses callbacks: you are given a continuation, and you call yield on it each time the old API calls back. Swift 6.4 adds a safer Continuation type for wrapping an API that calls back exactly once.

The open-source Swift Async Algorithms package adds operators such as debounce, throttle, merge and combineLatest. One limitation is worth knowing: an async sequence is normally consumed by a single for await loop. It does not broadcast to several listeners.

Example
func ticks(from ticker: Ticker) -> AsyncStream<Int> {
    AsyncStream { continuation in
        ticker.onTick = { count in
            continuation.yield(count)          // each callback becomes a value
        }
        continuation.onTermination = { _ in ticker.stop() }
        ticker.start()
    }
}

// Consumed like any sequence:   for await count in ticks(from: ticker) { ... }
Exercise

Wrap a delegate-based API you know well — location updates, a timer, keyboard notifications — in an AsyncStream, and consume it with for await inside a .task.

After this lesson you should be able to answer
  1. How do you turn a callback-based API into an AsyncSequence?
  2. How does a for await loop come to an end?
  3. Why can two loops usually not share one async sequence?
Resources