Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Don’t call us - we’ll call you

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Don’t call us - we’ll call you

AsyncSequence is a modern way to handle asynchronous events. In this talk, you will learn how it works both from a consumer and producer perspective, and how to use it to build modern APIs. We will also look at other ways do handle asynchronous events - prepare for a blast from the past!

Avatar for Peter Friese

Peter Friese

April 13, 2026

More Decks by Peter Friese

Other Decks in Technology

Transcript

  1. DeepDishSwift 2026 @peterfriese.dev Don ’ t call us - we

    ’ ll call you Peter Friese, Developer Relations Engineer, Google Modern SwiftUI callbacks using AsyncStream Follow me on Bluesky
  2. 10 PRINT "Hello world" 20 05 LET I = 1

    15 LET I = I + 1 GOTO 10
  3. 10 PRINT "Hello world" 20 05 LET I = 1

    15 LET I = I + 1 GOTO 10 IF I <= 5 THEN
  4. 10 PRINT "Hello world" 05 FOR I = 1 TO

    5 15 NEXT I Structured Programming
  5. /ˈsiːkw ə ns/ 1/ 2/ A set of related events,

    movements, or items that follow each other in a particular order. A particular order in which related things follow each other. A set of related events, movements, or items that follow each other in a particular order. A particular order in which related things follow each other.
  6. 1/ A particular order in which related things follow each

    other. for i in 1 .. . 5 { print("Hello, world!") } [ ] 1 2 3 4 5
  7. Content Warning The following slides contain scenes that may be

    extremely traumatizing to some audiences 􀋯
  8. 1/ A particular order in which related things follow each

    other. [ ] Dough Sauce Cheese Pepperoni 🍍 for item in ingredients { print("Adding \(item) .. . ") } let ingredients = ["Dough", "Sauce", "Cheese", "Pepperoni", "🍍"]
  9. Things you can do with sequences map / compactMap /

    flatMap / reduce / filter / first / last / contains / allSatisfy / min / max / prefix / suffix / drop / dropFirst / dropLast / split / sorted / reversed / shuffled / randomElement / enumerated / forEach / zip / joined / elementsEqual / starts / lexicographicallyPrecedes / lazy / firstIndex / lastIndex / count / isEmpty / makeIterator
  10. 1/ A particular order in which related things follow each

    other. [ ] Dough Sauce Cheese Pepperoni 🍍 for item in ingredients { print("Adding \(item) .. . ") } let ingredients = ["Dough", "Sauce", "Cheese", "Pepperoni", "🍍"] Let’s fix this
  11. for item in ingredients { print("Adding \(item) .. . ")

    } let ingredients = ["Dough", "Sauce", "Cheese", "Pepperoni", "🍍"] let approvedIngredients = ingredients.filter { $0 != "🍍" } [ ] Dough Sauce Cheese Pepperoni 🍍 approvedIngredients
  12. struct ToppingDispenser: Sequence, IteratorProtocol { var toppings = ["Tomato Sauce

    🍅", "Mozzarella 🧀", "Basil 🌿"] mutating func next() -> String? { guard !toppings.isEmpty else { return nil } return toppings.removeFirst() } } let dispenser = ToppingDispenser() for topping in dispenser { print("Adding \(topping) ... ") } print("Pizza is ready for the oven!") Let ’ s bake our own sequence
  13. /ˈsiːkw ə ns/ 1/ 2/ A set of related events,

    movements, or items that follow each other in a particular order. A particular order in which related things follow each other.
  14. Asynchronous Events 1/ Delegates (Objective-C) 2/ Callbacks (Objective-C) 3/ Delegates

    (Swift) 4/ Callbacks (Swift) 5/ Combine (Swift) 6/ Structured Concurrency (Swift)
  15. @protocol PizzaPlaceDelegate <NSObject> - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end

    @interface PizzaTracker : NSObject <PizzaPlaceDelegate> @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end Protocol 1/ Delegates (Objective-C)
  16. @protocol PizzaPlaceDelegate <NSObject> - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end

    @interface PizzaTracker : NSObject <PizzaPlaceDelegate> @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end Delegate method 1/ Delegates (Objective-C)
  17. @protocol PizzaPlaceDelegate <NSObject> - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end

    @interface PizzaTracker : NSObject <PizzaPlaceDelegate> @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end Interface 1/ Delegates (Objective-C)
  18. @protocol PizzaPlaceDelegate <NSObject> - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end

    @interface PizzaTracker : NSObject <PizzaPlaceDelegate> @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end Declare protocol conformance 1/ Delegates (Objective-C)
  19. Implementation didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end @interface PizzaTracker : NSObject <PizzaPlaceDelegate>

    @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end 1/ Delegates (Objective-C) @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; self.pizzaPlace.delegate = self; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID { self.currentStatus = status; } @end
  20. Our favourite pizza place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end @interface PizzaTracker

    : NSObject <PizzaPlaceDelegate> @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end 1/ Delegates (Objective-C) @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; self.pizzaPlace.delegate = self; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID { self.currentStatus = status; } @end
  21. Register as a delegate didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end @interface PizzaTracker

    : NSObject <PizzaPlaceDelegate> @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end 1/ Delegates (Objective-C) @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; self.pizzaPlace.delegate = self; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID { self.currentStatus = status; } @end
  22. Track our order didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end @interface PizzaTracker :

    NSObject <PizzaPlaceDelegate> @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end 1/ Delegates (Objective-C) @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; self.pizzaPlace.delegate = self; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID { self.currentStatus = status; } @end
  23. Receive updates didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end @interface PizzaTracker : NSObject

    <PizzaPlaceDelegate> @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end 1/ Delegates (Objective-C) @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; self.pizzaPlace.delegate = self; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID { self.currentStatus = status; } @end
  24. didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID; @end @interface PizzaTracker : NSObject <PizzaPlaceDelegate> @property

    (nonatomic, strong) PizzaPlace *pizzaPlace; @property (nonatomic, assign) PizzaStatus currentStatus; @end 1/ Delegates (Objective-C) @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; self.pizzaPlace.delegate = self; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } - (void)pizzaPlace:(PizzaPlace *)place didUpdateStatus:(PizzaStatus)status forOrderID:(NSString *)orderID { self.currentStatus = status; } @end
  25. typedef void (^PizzaStatusUpdateBlock)(PizzaStatus status); @interface PizzaPlace : NSObject @property (nonatomic,

    copy) PizzaStatusUpdateBlock onStatusUpdate; - (void)trackOrder:(NSString *)orderID; @end @implementation PizzaPlace - (void)trackOrder:(NSString *)orderID { if (self.onStatusUpdate) { self.onStatusUpdate(PizzaStatusOven); } } @end 2/ Callbacks (Objective-C) Typedef for our block
  26. typedef void (^PizzaStatusUpdateBlock)(PizzaStatus status); @interface PizzaPlace : NSObject @property (nonatomic,

    copy) PizzaStatusUpdateBlock onStatusUpdate; - (void)trackOrder:(NSString *)orderID; @end @implementation PizzaPlace - (void)trackOrder:(NSString *)orderID { if (self.onStatusUpdate) { self.onStatusUpdate(PizzaStatusOven); } } @end 2/ Callbacks (Objective-C) Property to hold on to the block / callback
  27. typedef void (^PizzaStatusUpdateBlock)(PizzaStatus status); @interface PizzaPlace : NSObject @property (nonatomic,

    copy) PizzaStatusUpdateBlock onStatusUpdate; - (void)trackOrder:(NSString *)orderID; @end @implementation PizzaPlace - (void)trackOrder:(NSString *)orderID { if (self.onStatusUpdate) { self.onStatusUpdate(PizzaStatusOven); } } @end 2/ Callbacks (Objective-C) Call the block
  28. @interface PizzaTracker : NSObject @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property

    (nonatomic, assign) PizzaStatus currentStatus; @end @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; _ _ weak typeof(self) weakSelf = self; self.pizzaPlace.onStatusUpdate = ^(PizzaStatus status) { weakSelf.currentStatus = status; }; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } @end 2/ Callbacks (Objective-C) Our favourite pizza place
  29. @interface PizzaTracker : NSObject @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property

    (nonatomic, assign) PizzaStatus currentStatus; @end @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; _ _ weak typeof(self) weakSelf = self; self.pizzaPlace.onStatusUpdate = ^(PizzaStatus status) { weakSelf.currentStatus = status; }; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } @end 2/ Callbacks (Objective-C) Register our callback
  30. @interface PizzaTracker : NSObject @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property

    (nonatomic, assign) PizzaStatus currentStatus; @end @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; _ _ weak typeof(self) weakSelf = self; self.pizzaPlace.onStatusUpdate = ^(PizzaStatus status) { weakSelf.currentStatus = status; }; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } @end 2/ Callbacks (Objective-C) Start tracking updates
  31. @interface PizzaTracker : NSObject @property (nonatomic, strong) PizzaPlace *pizzaPlace; @property

    (nonatomic, assign) PizzaStatus currentStatus; @end @implementation PizzaTracker - (void)startTracking { self.pizzaPlace = [[PizzaPlace alloc] init]; _ _ weak typeof(self) weakSelf = self; self.pizzaPlace.onStatusUpdate = ^(PizzaStatus status) { weakSelf.currentStatus = status; }; [self.pizzaPlace trackOrder:@"DEEP-DISH-2026"]; } @end 2/ Callbacks (Objective-C) Receive updates
  32. protocol PizzaPlaceDelegate: AnyObject { func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status:

    PizzaStatus, for orderID: String) } class PizzaPlace { weak var delegate: PizzaPlaceDelegate? func trackOrder(id: String) { delegate ?. pizzaPlace(self, didUpdateStatus: .oven, for: id) } } Protocol 3/ Delegates (Swift)
  33. protocol PizzaPlaceDelegate: AnyObject { func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status:

    PizzaStatus, for orderID: String) } class PizzaPlace { weak var delegate: PizzaPlaceDelegate? func trackOrder(id: String) { delegate ?. pizzaPlace(self, didUpdateStatus: .oven, for: id) } } Callback method 3/ Delegates (Swift)
  34. protocol PizzaPlaceDelegate: AnyObject { func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status:

    PizzaStatus, for orderID: String) } class PizzaPlace { weak var delegate: PizzaPlaceDelegate? func trackOrder(id: String) { delegate ?. pizzaPlace(self, didUpdateStatus: .oven, for: id) } } Property to hold on to the delegate 3/ Delegates (Swift)
  35. protocol PizzaPlaceDelegate: AnyObject { func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status:

    PizzaStatus, for orderID: String) } class PizzaPlace { weak var delegate: PizzaPlaceDelegate? func trackOrder(id: String) { delegate ?. pizzaPlace(self, didUpdateStatus: .oven, for: id) } } Send update to the callback 3/ Delegates (Swift)
  36. @Observable class PizzaTracker: PizzaPlaceDelegate { var pizzaPlace = PizzaPlace() var

    currentStatus: PizzaStatus? func startTracking() { pizzaPlace.delegate = self pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status: PizzaStatus, for orderID: String) { self.currentStatus = status } } Protocol conformance 3/ Delegates (Swift)
  37. @Observable class PizzaTracker: PizzaPlaceDelegate { var pizzaPlace = PizzaPlace() var

    currentStatus: PizzaStatus? func startTracking() { pizzaPlace.delegate = self pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status: PizzaStatus, for orderID: String) { self.currentStatus = status } } Our favourite pizza place 3/ Delegates (Swift)
  38. @Observable class PizzaTracker: PizzaPlaceDelegate { var pizzaPlace = PizzaPlace() var

    currentStatus: PizzaStatus? func startTracking() { pizzaPlace.delegate = self pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status: PizzaStatus, for orderID: String) { self.currentStatus = status } } Register as delegate 3/ Delegates (Swift)
  39. @Observable class PizzaTracker: PizzaPlaceDelegate { var pizzaPlace = PizzaPlace() var

    currentStatus: PizzaStatus? func startTracking() { pizzaPlace.delegate = self pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status: PizzaStatus, for orderID: String) { self.currentStatus = status } } Start tracking order 3/ Delegates (Swift)
  40. @Observable class PizzaTracker: PizzaPlaceDelegate { var pizzaPlace = PizzaPlace() var

    currentStatus: PizzaStatus? func startTracking() { pizzaPlace.delegate = self pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status: PizzaStatus, for orderID: String) { self.currentStatus = status } } Delegate method 3/ Delegates (Swift)
  41. @Observable class PizzaTracker: PizzaPlaceDelegate { var pizzaPlace = PizzaPlace() var

    currentStatus: PizzaStatus? func startTracking() { pizzaPlace.delegate = self pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } func pizzaPlace(_ place: PizzaPlace, didUpdateStatus status: PizzaStatus, for orderID: String) { self.currentStatus = status } } Receive updates 3/ Delegates (Swift)
  42. class PizzaPlace { var onStatusUpdate: ((PizzaStatus) -> Void)? func trackOrder(id:

    String) { onStatusUpdate?(.oven) } } @Observable class PizzaTracker { var pizzaPlace = PizzaPlace() var currentStatus: PizzaStatus? func startTracking() { pizzaPlace.onStatusUpdate = { [weak self] status in self ?. currentStatus = status } pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Closure 4/ Callbacks (Swift)
  43. class PizzaPlace { var onStatusUpdate: ((PizzaStatus) -> Void)? func trackOrder(id:

    String) { onStatusUpdate?(.oven) } } @Observable class PizzaTracker { var pizzaPlace = PizzaPlace() var currentStatus: PizzaStatus? func startTracking() { pizzaPlace.onStatusUpdate = { [weak self] status in self ?. currentStatus = status } pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Call the Closure 4/ Callbacks (Swift)
  44. class PizzaPlace { var onStatusUpdate: ((PizzaStatus) -> Void)? func trackOrder(id:

    String) { onStatusUpdate?(.oven) } } @Observable class PizzaTracker { var pizzaPlace = PizzaPlace() var currentStatus: PizzaStatus? func startTracking() { pizzaPlace.onStatusUpdate = { [weak self] status in self ?. currentStatus = status } pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Assign closure 4/ Callbacks (Swift)
  45. class PizzaPlace { var onStatusUpdate: ((PizzaStatus) -> Void)? func trackOrder(id:

    String) { onStatusUpdate?(.oven) } } @Observable class PizzaTracker { var pizzaPlace = PizzaPlace() var currentStatus: PizzaStatus? func startTracking() { pizzaPlace.onStatusUpdate = { [weak self] status in self ?. currentStatus = status } pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Receive status update 4/ Callbacks (Swift)
  46. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Publisher 5/ Combine
  47. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Subject 5/ Combine
  48. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } 5/ Combine
  49. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } 5/ Combine
  50. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Our favourite pizza place 5/ Combine
  51. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Publisher we can use to connect the UI 5/ Combine
  52. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } To clean up after us 5/ Combine
  53. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Connect to the status publisher on the pizza place (kitchen status board) 5/ Combine
  54. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Ensure we receive updates on the main thread 5/ Combine
  55. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Start the pipeline 5/ Combine
  56. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Assign received values to local state 5/ Combine
  57. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Store handle so we can clean up after ourselves 5/ Combine
  58. class PizzaPlace { private let statusSubject = PassthroughSubject<PizzaStatus, Never>() lazy

    var statusPublisher: AnyPublisher<PizzaStatus, Never> = statusSubject.eraseToAnyPublisher() func trackOrder(id: String) { statusSubject.send(.oven) } } class PizzaTracker: ObservableObject { let pizzaPlace = PizzaPlace() @Published var currentStatus: PizzaStatus? private var cancellables = Set<AnyCancellable>() func startTracking() { pizzaPlace.statusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] status in self ? . currentStatus = status } .store(in: &cancellables) pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } Start tracking our order (finally!) 5/ Combine
  59. class PizzaPlace { private let (stream, continuation) = AsyncStream.makeStream(of: PizzaStatus.self)

    var statusUpdates: AsyncStream<PizzaStatus> { stream } func trackOrder(id: String) { continuation.yield(.oven) / / When the pizza arrives, finish the stream: / / continuation.finish() } } Create a typed stream 6/ Structured Concurrency (Swift)
  60. Expose only the read-only stream 6/ Structured Concurrency (Swift) class

    PizzaPlace { private let (stream, continuation) = AsyncStream.makeStream(of: PizzaStatus.self) var statusUpdates: AsyncStream<PizzaStatus> { stream } func trackOrder(id: String) { continuation.yield(.oven) / / When the pizza arrives, finish the stream: / / continuation.finish() } }
  61. Send updates via the continuation 6/ Structured Concurrency (Swift) class

    PizzaPlace { private let (stream, continuation) = AsyncStream.makeStream(of: PizzaStatus.self) var statusUpdates: AsyncStream<PizzaStatus> { stream } func trackOrder(id: String) { continuation.yield(.oven) / / When the pizza arrives, finish the stream: / / continuation.finish() } }
  62. struct PizzaTrackerView: View { let pizzaPlace = PizzaPlace() @State private

    var currentStatus: PizzaStatus = .orderPlaced var body: some View { VStack(spacing: 20) { Text("Status: \(currentStatus)") .font(.largeTitle) Button("Check on Pizza") { pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } .task { for await status in pizzaPlace.statusUpdates { self.currentStatus = status } } } } Our favourite pizza place 6/ Structured Concurrency (Swift)
  63. struct PizzaTrackerView: View { let pizzaPlace = PizzaPlace() @State private

    var currentStatus: PizzaStatus = .orderPlaced var body: some View { VStack(spacing: 20) { Text("Status: \(currentStatus)") .font(.largeTitle) Button("Check on Pizza") { pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } .task { for await status in pizzaPlace.statusUpdates { self.currentStatus = status } } } } Start tracking the order 6/ Structured Concurrency (Swift)
  64. struct PizzaTrackerView: View { let pizzaPlace = PizzaPlace() @State private

    var currentStatus: PizzaStatus = .orderPlaced var body: some View { VStack(spacing: 20) { Text("Status: \(currentStatus)") .font(.largeTitle) Button("Check on Pizza") { pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } .task { for await status in pizzaPlace.statusUpdates { self.currentStatus = status } } } } Async stream of updates 6/ Structured Concurrency (Swift)
  65. struct PizzaTrackerView: View { let pizzaPlace = PizzaPlace() @State private

    var currentStatus: PizzaStatus = .orderPlaced var body: some View { VStack(spacing: 20) { Text("Status: \(currentStatus)") .font(.largeTitle) Button("Check on Pizza") { pizzaPlace.trackOrder(id: "DEEP-DISH-2026") } } .task { for await status in pizzaPlace.statusUpdates { self.currentStatus = status } } } } Just loop over the status update - how neat is that? 6/ Structured Concurrency (Swift)
  66. struct PizzaQueueView: View { @State private var orders = [Order]()

    @State private var snapshotListener: ListenerRegistration? var body: some View { List(orders) { order in Text(order.pizzaType) } .onAppear { subscribeToOrders() } .onDisappear { unsubscribeFromOrders() } } private func subscribeToOrders() { snapshotListener ?. remove() let query = Firestore.firestore().collection("orders") z` Cloud Firestore - Snapshot listeners
  67. .onDisappear { unsubscribeFromOrders() } } private func subscribeToOrders() { snapshotListener

    ?. remove() let query = Firestore.firestore().collection("orders") snapshotListener = query.addSnapshotListener { snapshot, error in guard let snapshot else { print("Error fetching snapshots: \(error!)") return } self.orders = snapshot.documents.compactMap { document in try? document.data(as: Order.self) } } } private func unsubscribeFromOrders() { snapshotListener ?. remove() } } Cloud Firestore - Snapshot listeners
  68. Streaming live data from Cloud Firestore let orderChanges = Firestore.firestore()

    .collection("orders") .snapshots .compactMap { snapshot in snapshot.documents.compactMap { documentSnapshot in try? documentSnapshot.data(as: Order.self) } } for try await orders in orderChanges { self.orders = orders } Sequence of snapshots
  69. Streaming live data from Cloud Firestore let orderChanges = Firestore.firestore()

    .collection("orders") .snapshots .compactMap { snapshot in snapshot.documents.compactMap { documentSnapshot in try? documentSnapshot.data(as: Order.self) } } for try await orders in orderChanges { self.orders = orders } Map all snapshots
  70. Streaming live data from Cloud Firestore let orderChanges = Firestore.firestore()

    .collection("orders") .snapshots .compactMap { snapshot in snapshot.documents.compactMap { documentSnapshot in try? documentSnapshot.data(as: Order.self) } } for try await orders in orderChanges { self.orders = orders } Convert Firestore document to Order (Codable!)
  71. Streaming live data from Cloud Firestore let orderChanges = Firestore.firestore()

    .collection("orders") .snapshots .compactMap { snapshot in snapshot.documents.compactMap { documentSnapshot in try? documentSnapshot.data(as: Order.self) } } for try await orders in orderChanges { self.orders = orders } Iterate over the stream of orders
  72. Combining streams Auth.auth() .authStateChanges .compactMap { $0 ? . uid

    } .flatMap { userId in } Firestore.firestore() .collection("orders") .whereField("customerId", isEqualTo: userId) .snapshots(includeMetadataChanges: false) .compactMap { snapshot in snapshot.documents.compactMap { documentSnapshot in try? documentSnapshot.data(as: Order.self) } }
  73. Combining streams Auth.auth() .authStateChanges .compactMap { $0 ? . uid

    } .flatMap { userId in } Firestore.firestore() .collection("orders") .whereField("customerId", isEqualTo: userId) .snapshots(includeMetadataChanges: false) .compactMap { snapshot in snapshot.documents.compactMap { documentSnapshot in try? documentSnapshot.data(as: Order.self) } } for try await orders in orderChanges { self.orders = orders }
  74. Combining streams Firestore.firestore() .collection("orders") .whereField("customerId", isEqualTo: userId) .snapshots(includeMetadataChanges: false) .compactMap

    { snapshot in snapshot.documents.compactMap { documentSnapshot in try? documentSnapshot.data(as: Order.self) } } for try await orders in orderChanges { self.orders = orders } Auth.auth() .authStateChanges .compactMap { $0 ? . uid } .flatMap { userId in } This doesn’t work 😳
  75. Combining streams Auth.auth() .authStateChanges .compactMap { $0 ? . uid

    } .flatMap { userId in } Firestore.firestore() .collection("orders") .whereField("customerId", isEqualTo: userId) .snapshots(includeMetadataChanges: false) .compactMap { snapshot in snapshot.documents.compactMap { documentSnapshot in try? documentSnapshot.data(as: Order.self) } } for try await orders in orderChanges { self.orders = orders } Latest flatMapLatest cancels and restarts the inner stream
  76. Why

  77. 05 LET I = 1 10 PRINT "Hello, world” 15

    LET I = I + 1 IF I <= 5 THEN
  78. 05 FOR I = 1 TO 5 10 PRINT "Hello,

    world" 15 NEXT I Structured Programming
  79. for element in sequence { / / do something with

    element } Structured Programming
  80. for try await element in sequence { / / do

    something with element } Structured Concurrency
  81. Resources Firebase SDK with AsyncStream support Slides for this talk

    https://github.com/firebase/firebase-ios-sdk https://speakerdeck.com/peterfriese Repo with sample projects https://bit.ly/dont-call-us-repo Livestream https://www.youtube.com/@PeterFriese/streams