#a for-in Syntax 13 for year in 2016...2024 { print("iOSDC Japan \(year)") } // iOSDC Japan 2016 // iOSDC Japan 2017 // ... // iOSDC Japan 2023 // iOSDC Japan 2024
#a for-in Syntax → while Syntax 26 for remaining in Countdown(count: 3) { print(remaining) } var iterator = Countdown(count: 3).makeIterator() while let remaining = iterator.next() { print(remaining) }
#a Overview Sequence • Arrays, dictionaries, strings, and other types conform to the Sequence • Instances that conform to Sequence can be used in a for-in loop • The Sequence provides many built-in methods for manipulating elements • Creating a custom type that conforms to Sequence requires implementing an Iterator 27
#a for-await-in Syntax ← while Syntax 39 for await remaining in Countdown(count: 3) { print(remaining) } var iterator = Countdown(count: 3).makeAsyncIterator() while let remaining = await iterator.next() { print(remaining) }
#a for-await-in Syntax with where Clause 43 // Swift 5.5 <= 5.10 let sequence: some AsyncSequence = ... for await element in sequence where condition { print(element) }
#a while Syntax 50 var iterator = AsyncStorageDataSequence(storage: ...).makeAsyncIterator() while let data = try await iterator.next() { print(data) }
#a for-await-in Syntax ← while Syntax 51 for await data in AsyncStorageDataSequence(storage: ...) { print(data) } var iterator = AsyncStorageDataSequence(storage: ...).makeAsyncIterator() while let data = try await iterator.next() { print(data) }
#a for-try-await-in Syntax ← while Syntax 52 for try await data in AsyncStorageDataSequence(storage: ...) { print(data) } var iterator = AsyncStorageDataSequence(storage: ...).makeAsyncIterator() while let data = try await iterator.next() { print(data) }
#a Overview AsyncSequence • AsyncSequence is just like Sequence, except elements are provided asynchronously • Instances that conform to AsyncSequence can be used in a for-await-in loop • The AsyncSequence provides many of the same built-in methods for manipulating elements as Sequence • May throw error 54
#a Types of Apple’s AsyncSequence APIs Based on My Perspective • Adopted as an interface for entirely new functionality • Added the interfaces to existing APIs: • Closure-based • Delegate-based • Selector-based • Observer-based • As a Combine Publisher 57
#a Types of Apple’s AsyncSequence APIs Based on My Perspective • Adopted as an interface for entirely new functionality • Added the interfaces to existing APIs: • Closure-based • Delegate-based • Selector-based • Observer-based • As a Combine Publisher 58
#a Types of Apple’s AsyncSequence APIs Based on My Perspective • Adopted as an interface for entirely new functionality • Added the interfaces to existing APIs: • Closure-based • Delegate-based • Selector-based • Observer-based • As a Combine Publisher 62
#a Types of Apple’s AsyncSequence APIs Based on My Perspective • Adopted as an interface for entirely new functionality • Added the interfaces to existing APIs: • Closure-based • Delegate-based • Selector-based • Observer-based • As a Combine Publisher 67
#a Types of Apple’s AsyncSequence APIs Based on My Perspective • Adopted as an interface for entirely new functionality • Added the interfaces to existing APIs: • Closure-based • Delegate-based • Selector-based • Observer-based • As a Combine Publisher 71
#a AsyncPublisher / AsyncThrowingPublisher Publisher.values 72 import Combine let publisher = CurrentValueSubject<Int, Never>(2024) let cancellable = publisher.sink { value in print(value) } // 2024 let task = Task { for await value in publisher.values { print(value) } // 2024 }
#a Different Results Publisher.sink(receiveValue:) vs. Publisher.values 73 import Combine let publisher = CurrentValueSubject<Int, Never>(0) let cancellable = publisher.sink { value in print(value) } // 0 1 2 3 let task = Task { for await value in publisher.values { print(value) } // 0 1 3 } // after the value is ready to be received publisher.send(1) publisher.send(2) publisher.send(3)
#a Different Results Publisher.sink(receiveValue:) vs. Publisher.values 75 import Combine let publisher = CurrentValueSubject<Int, Never>(0) let cancellable = publisher.sink { value in print(value) } // 0 1 2 3 let task = Task { for await value in publisher .values { print(value) } // 0 1 3 } // after the value is ready to be received publisher.send(1) publisher.send(2) publisher.send(3)
#a Same Results Publisher.sink(receiveValue:) vs. Publisher.values 76 import Combine let publisher = CurrentValueSubject<Int, Never>(0) let cancellable = publisher.sink { value in print(value) } // 0 1 2 3 let task = Task { for await value in publisher .buffer(size: 100, prefetch: .keepFull, whenFull: .dropOldest) .values { print(value) } // 0 1 2 3 } // after the value is ready to be received publisher.send(1) publisher.send(2) publisher.send(3)
#a Same Results Publisher.sink(receiveValue:) vs. Publisher.values 77 import Combine let publisher = CurrentValueSubject<Int, Never>(0) let cancellable = publisher.sink { value in print(value) } // 0 1 2 3 let task = Task { for await value in publisher .buffer(size: 100, prefetch: .keepFull, whenFull: .dropOldest) .values { print(value) } // 0 1 2 3 } // after the value is ready to be received publisher.send(1) publisher.send(2) publisher.send(3) unlimited
#a Any Other Way? Publisher.sink(receiveValue:) 78 import Combine let publisher = CurrentValueSubject<Int, Never>(0) let cancellable = publisher.sink { value in print(value) } // ??????????? // after the value is ready to be received publisher.send(1) publisher.send(2) publisher.send(3)
#a Adapting Existing Code From Closure-based 80 import Combine let publisher = CurrentValueSubject<Int, Never>(0) var cancellable: AnyCancellable? let value = await withCheckedContinuation { continuation in cancellable = publisher.sink { value in continuation.resume(returning: value) } } print(value) // after the value is ready to be received publisher.send(1) publisher.send(2) publisher.send(3)
#a Adapting Existing Code From Closure-based 81 import Combine let publisher = CurrentValueSubject<Int, Never>(0) var cancellable: AnyCancellable? let value = await withCheckedContinuation { continuation in cancellable = publisher.sink { value in continuation.resume(returning: value) } } print(value) // after the value is ready to be received publisher.send(1) publisher.send(2) publisher.send(3) Thread 1 : Fatal error: SWIFT TASK CONTINUATION MISUSE : * tried to resume its continuation more than once, returning 1!
#a Produce the Result • For emitting a single value • withCheckedContinuation(function:_:) withCheckedThrowingContinuation(function:_:) • For emitting a multiple value • AsyncSequence 82
#a Adapting Existing Code From Closure-based 84 import Combine let publisher = CurrentValueSubject<Int, Never>(0) let (stream, continuation) = AsyncStream.makeStream(of: Int.self) let cancellable = publisher.sink { value in continuation.yield(value) } continuation.onTermination = { _ in cancellable.cancel() } let task = Task { for await value in stream { print(value) } // 0 1 2 3 } // after the value is ready to be received publisher.send(1) publisher.send(2) publisher.send(3) let task = Task { for await value in stream { print(value) } // 0 1 2 3 } // after the value is ready to be received publisher.send(1) publisher.send(2) publisher.send(3)
#a deinit May not be Called Strong Reference Capturing in the Task 103 final class ViewController: UIViewController { let presenter = Presenter() private var task: Task<(), Never>? deinit { ... } override func viewDidLoad() { super.viewDidLoad() task = Task { for await ... in ... { presenter.didReceiveKeyboardWillShowNotification() } } } }
#a deinit May not be Called Strong Reference Capturing in the Task 104 final class ViewController: UIViewController { let presenter = Presenter() private var task: Task<(), Never>? deinit { ... } override func viewDidLoad() { super.viewDidLoad() task = Task { for await ... in ... { presenter.didReceiveKeyboardWillShowNotification() } } } } Where is “self.” ...?
#a deinit May not be Called Strong Reference Capturing in the Task 105 final class ViewController: UIViewController { let presenter = Presenter() private var task: Task<(), Never>? deinit { ... } override func viewDidLoad() { super.viewDidLoad() task = Task { for await ... in ... { presenter.didReceiveKeyboardWillShowNotification() } } } } Where is “self.” ...? @_implicitSelfCapture
#a Like the Combine sink treastrain/AsyncSequenceSubscription 110 import AsyncSequenceSubscription import Foundation let sequence: some AsyncSequence = ... let task = sequence .sink { [weak self] element in await self?.didReceived(element) }
#a Can Call Cancel Using the Task treastrain/AsyncSequenceSubscription 111 import AsyncSequenceSubscription import Foundation let sequence: some AsyncSequence = ... let task = sequence .sink { [weak self] element in await self?.didReceived(element) } // ... task.cancel()
#a Auto Cancelling storeWhileInstanceActive(_:) 112 import AsyncSequenceSubscription import Foundation let sequence: some AsyncSequence = ... sequence .sink { [weak self] element in await self?.didReceived(element) } .storeWhileInstanceActive(self)
#a Benefits of moving to AsyncSequence Simplifying Async Method Calls • Calling async methods from closures, delegate methods, or Combine publishers required creating a Task • This was necessary because these contexts are not within the async/await world • With AsyncSequence, using a for-await-in loop means you’re already within a Task, so no need to create one 117
#a Benefits of moving to AsyncSequence Navigating Threading Rules with async/await • Delegate methods may be called from threads other than the main thread, as documented • In Combine, values may arrive on a non-main thread, requiring the following processing on the main thread • Violating this rule could result in runtime warnings from the Main Thread Checker or unexpected behavior 119
#a Benefits of moving to AsyncSequence DispatchQueue.async & Publisher.receive(on:) 120 import Combine // Needs to be called from the main thread func mainFunc(value: Int) {} func exec() { let cancellable1 = Just(10).sink { value in DispatchQueue.main.async { mainFunc(value: value) } } let cancellable2 = Just(10).receive(on: DispatchQueue.main).sink { value in mainFunc(value: value) } // ... }
#a Loooooong Type Name Methods return a type specific to the method’s semantics 125 // Countdown let sequence1 = Countdown(count: 1) // AsyncMapSequence<Countdown, UInt> let sequence2 = sequence1.map { $0 * 2 } // AsyncFilterSequence<AsyncMapSequence<Countdown, UInt>> let sequence3 = sequence2.filter { $0 > 6 } // AsyncDropFirstSequence<AsyncFilterSequence<AsyncMapSequence<Countdown, UI let sequence4 = sequence3.dropFirst() // AsyncPrefixSequence<AsyncDropFirstSequence<AsyncFilterSequence<AsyncMapSe let sequence5 = sequence4.prefix(1)
#a some AsyncSequence It's not cool to use 130 // Countdown (no-throwing) let sequence: some AsyncSequence = Countdown(count: 1) // ❌ Call can throw, but the error is not handled - Insert 'try' for try await remaining in sequence { // ❌ Cannot convert value of type '(some AsyncSequence).Element' to specified type 'UInt' - Insert ' as! UInt' let num: UInt = remaining print(remaining) }