$30 off During Our Annual Pro Sale. View Details »

Mastering AsyncSequence - 使う・作る・他のデザインパターン(クロージ...

Mastering AsyncSequence - 使う・作る・他のデザインパターン(クロージャ、Delegate など)から移行する

Mastering AsyncSequence - 使う・作る・他のデザインパターン(クロージャ、Delegate など)から移行する

iOSDC Japan 2024 
Day 2
2024/08/24 13:00〜 Track A レギュラートーク(40分)
https://fortee.jp/iosdc-japan-2024/proposal/c0c62dc1-992c-4d9f-8812-02994593d96c

treastrain / Tanaka Ryoga

August 24, 2024
Tweet

More Decks by treastrain / Tanaka Ryoga

Other Decks in Programming

Transcript

  1. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a ࢖͏ɾ࡞ΔɾଞͷσβΠϯύλʔϯʢΫϩʔδϟɺDelegate ͳͲʣ͔ΒҠߦ͢Δ Mastering AsyncSequence 1 iOSDC Japan 2024 Day 2 #iosdc #a treastrain / Tanaka Ryoga
  2. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a About me treastrain / Tanaka Ryoga Swift 🦅 / Core NFC 📶 - Japan NFC Reader 💳 / PadDisplay 📺 @treastrain https://tret.jp DeNA Co., Ltd. - iOS App Developer ( April 2021 - Current) 2
  3. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a My Previous Talks at iOSDC Japan Not a Sequel to Today’s Talk 3
  4. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 5 AsyncSequence | Apple Developer Documentation https://developer.apple.com/documentation/swift/asyncsequence ~ 5.10
  5. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 6 AsyncSequence | Apple Developer Documentation https://developer.apple.com/documentation/swift/asyncsequence ~ 5.10
  6. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Outline • What is AsyncSequence? • Usage • Adopting AsyncSequence • Swift 6.0 Updates 7
  7. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 10 Sequence | Apple Developer Documentation https://developer.apple.com/documentation/swift/sequence
  8. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming Types Sequence Array Dictionary Range / ClosedRange Set String 12 …etc.
  9. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  10. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a for-in Syntax with where Clause 14 let string = "DeNA" for character in string where character.isUppercase { print(character) } // D // N // A
  11. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Built-in Sequence Implementations 15 let odds = [1, 3, 5, 7, 9] let evens = odds.map { $0 + 1 } print(evens) // [2, 4, 6, 8, 10] let fullName = "Tanaka Ryoga" let initials = fullName.filter(\.isUppercase) print(initials) // TR
  12. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the Sequence 19 struct Countdown { // ... }
  13. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the Sequence 20 struct Countdown: Sequence { // ... }
  14. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the Sequence 21 struct Countdown: Sequence { struct Iterator { // ... } // ... }
  15. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the Sequence 22 struct Countdown: Sequence { struct Iterator: IteratorProtocol { // ... } // ... }
  16. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the Sequence 23 struct Countdown: Sequence { struct Iterator: IteratorProtocol { var count: UInt mutating func next() -> UInt? { guard count > 0 else { return nil } defer { count -= 1 } return count } } // ... }
  17. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the Sequence 24 struct Countdown: Sequence { struct Iterator: IteratorProtocol { // ... } func makeIterator() -> Iterator { Iterator(count: count) } let count: UInt }
  18. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the Sequence 25 struct Countdown: Sequence { // ... let count: UInt } for remaining in Countdown(count: 3) { print(remaining) } // 3 // 2 // 1
  19. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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) }
  20. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  21. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 31 AsyncSequence | Apple Developer Documentation https://developer.apple.com/documentation/swift/asyncsequence ~ 5.10
  22. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Providing async-iterated Access 32 struct Countdown: Sequence { struct Iterator: IteratorProtocol { var count: UInt mutating func next() -> UInt? { guard count > 0 else { return nil } defer { count -= 1 } return count } } // ... }
  23. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Providing async-iterated Access 33 struct Countdown: Sequence { struct Iterator: IteratorProtocol { var count: UInt mutating func next() async -> UInt? { guard count > 0 else { return nil } defer { count -= 1 } return count } } // ... }
  24. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the AsyncSequence 34 struct Countdown: AsyncSequence { struct AsyncIterator: AsyncIteratorProtocol { var count: UInt mutating func next() async -> UInt? { guard count > 0 else { return nil } defer { count -= 1 } return count } } // ... }
  25. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the AsyncSequence 35 struct Countdown: AsyncSequence { struct AsyncIterator: AsyncIteratorProtocol { var count: UInt mutating func next() async -> UInt? { guard count > 0 else { return nil } defer { count -= 1 } try? await Task.sleep(for: .seconds(1)) return count } } // ... }
  26. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Conforming to the AsyncSequence 36 struct Countdown: AsyncSequence { struct AsyncIterator: AsyncIteratorProtocol { // ... } func makeAsyncIterator() -> AsyncIterator { AsyncIterator(count: count) } let count: UInt }
  27. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a while Syntax 37 var iterator = Countdown(count: 3).makeIterator() while let remaining = iterator.next() { print(remaining) }
  28. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a while Syntax 38 var iterator = Countdown(count: 3).makeAsyncIterator() while let remaining = await iterator.next() { print(remaining) }
  29. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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) }
  30. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a for-await-in Syntax 40 // Swift 5.5 <= 5.10 let sequence: some AsyncSequence = ... for await element in sequence { print(element) }
  31. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a for-await-in Syntax 41 // Swift 5.5 <= 5.10 let sequence: some AsyncSequence = ... for await element in sequence { if condition { break } }
  32. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a for-await-in Syntax 42 // Swift 5.5 <= 5.10 let sequence: some AsyncSequence = ... for await element in sequence { if condition { continue } }
  33. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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) }
  34. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Built-in AsyncSequence Implementations 44 // Swift 5.5 <= 5.10 var sequence: some AsyncSequence { ... } let mapped = sequence.map { transform } let filtered = sequence.filter { isIncluded }
  35. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Throwing Errors 46 struct AsyncStorageDataSequence: AsyncSequence { struct AsyncIterator: AsyncIteratorProtocol { let storage: Storage mutating func next() async -> Data? { try? await Task.sleep(for: .seconds(1)) return await storage.data() } } // ... }
  36. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Throwing Errors 47 struct AsyncStorageDataSequence: AsyncSequence { struct AsyncIterator: AsyncIteratorProtocol { let storage: Storage mutating func next() async -> Data? { try? await Task.sleep(for: .seconds(1)) return await storage.data() } } // ... } struct Storage { func data() async throws -> Data { ... } }
  37. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Throwing Errors 48 struct AsyncStorageDataSequence: AsyncSequence { struct AsyncIterator: AsyncIteratorProtocol { let storage: Storage mutating func next() async throws -> Data? { try await Task.sleep(for: .seconds(1)) return try await storage.data() } } // ... } struct Storage { func data() async throws -> Data { ... } }
  38. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a while Syntax 49 var iterator = AsyncStorageDataSequence(storage: ...).makeAsyncIterator() while let data = await iterator.next() { print(data) }
  39. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a while Syntax 50 var iterator = AsyncStorageDataSequence(storage: ...).makeAsyncIterator() while let data = try await iterator.next() { print(data) }
  40. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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) }
  41. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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) }
  42. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a do-catch + for-try-await-in 53 do { for try await data in AsyncStorageDataSequence(storage: ...) { print(data) } } catch { // Error handling }
  43. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  44. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a AsyncSequence APIs in Apple SDK 55 What’s new for Apple developers - Apple Developer https://developer.apple.com/whats-new/
  45. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 56 ncLineSequenceɹURLSession.AsyncBytesɹActivity.ContentUpda e.MessagesɹTaskGroupɹThrowingTaskGroupɹTransaction.Trans FrameProvider.CameraFrameUpdatesɹTranslationSession.BatchRe ManagedAppsɹAsyncStreamɹAsyncThrowingStreamɹDockAcce AssetImageGenerator.ImagesɹNWPathMonitorɹARKitSession.Eve ate.UpdatesɹAsyncPublisherɹAsyncThrowingPublisher ɹCSSearc Session.EventStreamɹMusicSubscription.UpdatesɹSHSession.Re aracterSequenceɹNotificationCenter.NotificationsɹFileHandle.As
  46. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  47. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  48. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a TranslationSession.translations(from:) Sequence - [ TranslationSession.Response] 59 import Translation import SwiftUI struct ContentView: View { let contents: [String] = ["Jelly Bean", "KitKat", "Lollipop"] @State private var translated: [Int : String] = [:] var body: some View { List(contents.indices, id: \.self) { index in Text(translated[index] ?? contents[index]) } .translationTask { session in let requests: [TranslationSession.Request] = contents.indices.map { let content = contents[$0] return .init(sourceText: content, clientIdentifier: "\($0)") } do { let responses = try await session.translations(from: requests) for response in responses { let index = Int(response.clientIdentifier!)! translated[index] = response.targetText } } catch { // Error handling } } } } let responses = try await session.translations(from: requests) for response in responses { let index = Int(response.clientIdentifier!)! translated[index] = response.targetText }
  49. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a TranslationSession.translations(from:) Sequence - [ TranslationSession.Response] 60 let responses = try await session.translations(from: requests) for response in responses { let index = Int(response.clientIdentifier!)! translated[index] = response.targetText } Jelly Bean KitKat Lollipop translations(from:) δΣϦʔϏʔϯ ΩοτΧοτ ๮͖ͭΞΠεΩϟϯσΟ
  50. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a TranslationSession.translate(batch:) AsyncSequence - TranslationSession.BatchResponse 61 for try await response in session.translate(batch: requests) { let index = Int(response.clientIdentifier!)! translated[index] = response.targetText } Jelly Bean KitKat Lollipop translate(batch:) δΣϦʔϏʔϯ ΩοτΧοτ ๮͖ͭΞΠεΩϟϯσΟ
  51. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  52. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a NWPathMonitor Closure-based 63 import Network import SwiftUI struct ContentView: View { @State private var interface: String = "Checking..." @State private var monitor: NWPathMonitor! var body: some View { Text(interface) .onAppear { monitor = NWPathMonitor() monitor.pathUpdateHandler = { path in if path.usesInterfaceType(.wifi) { interface = "Wi-Fi" } else if path.usesInterfaceType(.cellular) { interface = "Cellular" } else if path.status == .satisfied { interface = "Others" } else { interface = "No available" } } monitor.start(queue: .main) } } } monitor = NWPathMonitor() monitor.pathUpdateHandler = { path in if path.usesInterfaceType(.wifi) { interface = "Wi-Fi" } else if path.usesInterfaceType(.cellular) { interface = "Cellular" } else if path.status == .satisfied { interface = "Others" } else { interface = "No available" } } monitor.start(queue: .main)
  53. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a NWPathMonitor Closure - pathUpdateHandler: ( ( NWPath) -> Void)? 64 monitor = NWPathMonitor() monitor.pathUpdateHandler = { path in if path.usesInterfaceType(.wifi) { interface = "Wi-Fi" } else if path.usesInterfaceType(.cellular) { interface = "Cellular" } else if path.status == .satisfied { interface = "Others" } else { interface = "No available" } } monitor.start(queue: .main)
  54. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a NWPathMonitor AsyncSequence - NWPathMonitor 65 for await path in NWPathMonitor() { if path.usesInterfaceType(.wifi) { interface = "Wi-Fi" } else if path.usesInterfaceType(.cellular) { interface = "Cellular" } else if path.status == .satisfied { interface = "Others" } else { interface = "No available" } }
  55. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a NWPathMonitor Closure-based → AsyncSequence 66 import Network import SwiftUI struct ContentView: View { @State private var interface: String = "Checking..." var body: some View { Text(interface) .task { for await path in NWPathMonitor() { if path.usesInterfaceType(.wifi) { interface = "Wi-Fi" } else if path.usesInterfaceType(.cellular) { interface = "Cellular" } else if path.status == .satisfied { interface = "Others" } else { interface = "No available" } } } } } for await path in NWPathMonitor() { if path.usesInterfaceType(.wifi) { interface = "Wi-Fi" } else if path.usesInterfaceType(.cellular) { interface = "Cellular" } else if path.status == .satisfied { interface = "Others" } else { interface = "No available" } }
  56. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  57. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Handling Location Updates Delegate - CLLocationManagerDelegate 68 import CoreLocation final class LocationHelper: NSObject { let manager = CLLocationManager() override init() { super.init() manager.delegate = self // ... } func listen() { manager.startUpdatingLocation() } } extension LocationHelper: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { print("Current locations:", locations) } func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) { // Error handling } // ... }
  58. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Handling Location Updates AsyncSequence - CLLocationUpdate.Updates 69 import CoreLocation final class LocationHelper: NSObject { override init() { super.init() // ... } func listen() async { do { for try await update in CLLocationUpdate.liveUpdates() { print("Current location:", update.location) // ... } } catch { // Error handling } } } func listen() async { do { for try await update in CLLocationUpdate.liveUpdates() { print("Current location:", update.location) // ... } } catch { // Error handling } }
  59. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Handling Location Updates AsyncSequence - CLLocationUpdate.Updates 70 func listen() async { do { for try await update in CLLocationUpdate.liveUpdates() { print("Current location:", update.location) // ... } } catch { // Error handling } }
  60. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  61. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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 }
  62. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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)
  63. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 74 https://x.com/shimastriper/status/1782505026503753951
  64. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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)
  65. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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)
  66. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  67. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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)
  68. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Adapting Existing Code ( Closure-based) 79
  69. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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)
  70. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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!
  71. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Produce the Result • For emitting a single value • withCheckedContinuation(function:_:) withCheckedThrowingContinuation(function:_:) • For emitting a multiple value • AsyncSequence 82
  72. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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)
  73. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Adapting Existing Code ( Delegate-based) 85
  74. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Adapting Existing Code From Delegate-based 86 protocol MyDelegate { func didUpdate(value: Int) func didFinish() func didFail(with error: any Error) } final class MyController { ... } extension MyController: MyDelegate { func didUpdate(value: Int) { ... } func didFinish() { ... } func didFail(with error: any Error) { ... } }
  75. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Adapting Existing Code From Delegate-based 87 protocol MyDelegate { ... } final class MyController { private var continuation: AsyncThrowingStream<Int, any Error>.Continuation? var stream: AsyncThrowingStream<Int, any Error> { let (stream, continuation) = AsyncThrowingStream.makeStream(of: Int.self) self.continuation = continuation return stream } } extension MyController: MyDelegate { ... }
  76. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Adapting Existing Code From Delegate-based 88 protocol MyDelegate { ... } final class MyController { ... } extension MyController: MyDelegate { func didUpdate(value: Int) { continuation?.yield(value) } func didFinish() { ... } func didFail(with error: any Error) { ... } }
  77. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Adapting Existing Code From Delegate-based 89 protocol MyDelegate { ... } final class MyController { ... } extension MyController: MyDelegate { func didUpdate(value: Int) { ... } func didFinish() { continuation?.finish() continuation = nil } func didFail(with error: any Error) { ... } }
  78. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Adapting Existing Code From Delegate-based 90 protocol MyDelegate { ... } final class MyController { ... } extension MyController: MyDelegate { func didUpdate(value: Int) { ... } func didFinish() { ... } func didFail(with error: any Error) { continuation?.finish(throwing: error) continuation = nil } }
  79. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Adapting Existing Code From Delegate-based 91 protocol MyDelegate { func didUpdate(value: Int) func didFinish() func didFail(with error: any Error) } final class MyController { private var continuation: AsyncThrowingStream<Int, any Error>.Continuation? var stream: AsyncThrowingStream<Int, any Error> { let (stream, continuation) = AsyncThrowingStream.makeStream(of: Int.self) self.continuation = continuation return stream } } extension MyController: MyDelegate { func didUpdate(value: Int) { continuation?.yield(value) } func didFinish() { continuation?.finish() continuation = nil } func didFail(with error: any Error) { continuation?.finish(throwing: error) continuation = nil } }
  80. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Swift Concurrency Naming async/await • noun • e.g. URLSession.data(from:delegate:) • No verbs used • 🙅 URLSession.getData(from:delegate:) 94
  81. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a AsyncSequence Variable Naming Apple SDK • bytes • updates • events • messages • *Stream 95
  82. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a AsyncSequence Type Naming Apple SDK • Updates: ActivityStateUpdates, DeviceMotionUpdates • Statuses: Product.SubscriptionInfo.Status.Statuses • Results: CSSearchQuery.Results, HKWorkoutRouteQueryDescriptor.Results • Messages: GroupSessionMessenger.Messages, Transaction.Transactions • Events: AccessoryEvents, CLMonitor.Events • Transactions: Transaction.Transactions • Notifications: NotificationCenter.Notifications 96
  83. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a AsyncSequence Type Naming Apple SDK • AsyncBytes • AsyncCharacterSequence • AsyncLineSequence 97
  84. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a NotificationCenter.Notifications UIResponder.keyboardWillShowNotification 99 NotificationCenter .default .notifications(named: UIResponder.keyboardWillShowNotification)
  85. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Subscribe viewDidLoad() 100 import UIKit final class ViewController: UIViewController { let presenter = Presenter() override func viewDidLoad() { super.viewDidLoad() Task { for await notification in NotificationCenter .default .notifications( named: UIResponder.keyboardWillShowNotification ) { presenter.didReceiveKeyboardWillShowNotification() } } } }
  86. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Stopping Event Listening Prevent Memory Leaks in for-await-in 101 import UIKit final class ViewController: UIViewController { let presenter = Presenter() override func viewDidLoad() { super.viewDidLoad() Task { for await notification in NotificationCenter .default .notifications( named: UIResponder.keyboardWillShowNotification ) { presenter.didReceiveKeyboardWillShowNotification() } } } } ⏳
  87. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Cancel at deinit For Stopping for-await-in 102 import UIKit final class ViewController: UIViewController { let presenter = Presenter() private var task: Task<(), Never>? deinit { task?.cancel() } override func viewDidLoad() { super.viewDidLoad() task = Task { for await notification in NotificationCenter { ... } } } }
  88. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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() } } } }
  89. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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.” ...?
  90. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  91. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a [weak self] with self?.* 106 final class ViewController: UIViewController { let presenter = Presenter() var anotherValue = 0 private var task: Task<(), Never>? deinit { ... } override func viewDidLoad() { super.viewDidLoad() task = Task { [weak self] in for await ... in ... { self?.presenter.didReceiveKeyboardWillShowNotification() anotherValue = 10 } } } } 🚨
  92. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a [weak object] Capturing the target property 107 final class ViewController: UIViewController { let presenter = Presenter() private var task: Task<(), Never>? deinit { ... } override func viewDidLoad() { super.viewDidLoad() task = Task { [weak presenter] in for await ... in ... { presenter?.didReceiveKeyboardWillShowNotification() } } } }
  93. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Static Analyzer…? It’s pernickety… 108 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() } } } }
  94. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 109 https://github.com/treastrain/AsyncSequenceSubscription treastrain/AsyncSequenceSubscription
  95. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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) }
  96. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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()
  97. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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)
  98. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a SwiftUI Easy Cancelling 113 import SwiftUI struct ContentView: View { var body: some View { Text("Hello") .task { for await notification in NotificationCenter .default .notifications( named: UIResponder.keyboardWillShowNotification ) { // ... } } } }
  99. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 114 task(priority:_ : ) | Apple Developer Documentation https://developer.apple.com/documentation/swiftui/view/task(priority:_:)
  100. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Benefits of moving to AsyncSequence Swift Everywhere 116 🚜 Apple Platforms Only
  101. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  102. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 118 https://speakerdeck.com/treastrain/subscribe-closure-when-calling-an-async-method-there-d11f9679 - 7abe-4c29-b837-eca98ef9a985
  103. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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
  104. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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) } // ... }
  105. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Benefits of moving to AsyncSequence Async/await & @ MainActor 121 import Combine @MainActor func mainFunc(value: Int) {} func exec() { let task = Task { for await value in Just(10).values { await mainFunc(value: value) } } // ... }
  106. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Built-in AsyncSequence Implementations 123 // Swift 5.5 <= 5.10 var sequence: some AsyncSequence { ... } let mapped = sequence.map { transform } let filtered = sequence.filter { isIncluded }
  107. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 124 https://github.com/treastrain/AsyncSequenceSubscription apple/swift-async-algorithms
  108. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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)
  109. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Type Erasure SwiftUI / Combine 126 🚜 AnyPublisher eraseToAnyPublisher() AnyView some View
  110. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a AsyncSequence next() async or next() async throws 128 struct MyAsyncSequence: AsyncSequence { struct AsyncIterator: AsyncIteratorProtocol { mutating func next() async -> Element? { } } func makeAsyncIterator() -> AsyncIterator { AsyncIterator() } } struct MyAsyncSequence: AsyncSequence { struct AsyncIterator: AsyncIteratorProtocol { mutating func next() async throws -> Element? { } } func makeAsyncIterator() -> AsyncIterator { AsyncIterator() } }
  111. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 129 AsyncSequence | Apple Developer Documentation https://developer.apple.com/documentation/swift/asyncsequence ~ 5.10
  112. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #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) }
  113. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 131 AsyncSequence | Apple Developer Documentation https://developer.apple.com/documentation/swift/asyncsequence ~ 5.10
  114. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a 132 AsyncSequence | Apple Developer Documentation https://developer.apple.com/documentation/swift/asyncsequence 6.0
  115. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Generalize Effect Polymorphism SE - 0421 • SE - 0388 : Convenience Async[Throwing]Stream.makeStream methods • SE - 0413 : Typed throws • SE - 0414 : Region based Isolation 133 struct MyAsyncSequence: AsyncSequence<Element, Failure> { struct AsyncIterator: AsyncIteratorProtocol<Element, Failure> { mutating func next() async throws(Failure) -> Element? { } } func makeAsyncIterator() -> AsyncIterator { AsyncIterator() } } throws(Never)
  116. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a some AsyncSequence<Element, Failure> Opaque types are practical 134 // Countdown (no-throwing) let sequence: some AsyncSequence<UInt, Never> = Countdown(count: 1) .map { $0 * 2 } .filter { $0 > 6 } .dropFirst() .prefix(1) // ✅ for await remaining in Countdown(count: 1) { print(remaining) // UInt }
  117. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a Recap • Sequence 🫱 🫲 AsyncSequence • AsyncSequence APIs included in Apple's SDK • Closure → AsyncSequence / Delegate → AsyncSequence • Practical Guide 135
  118. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a References • SE - 0298 : Async/Await: Sequences https://github.com/swiftlang/swift-evolution/blob/main/proposals/0298- asyncsequence.md • SE - 0388 : Convenience Async[Throwing]Stream.makeStream methods https://github.com/swiftlang/swift- evolution/blob/main/proposals/0388-async-stream-factory.md • SE - 0421 : Generalize effect polymorphism for AsyncSequence and AsyncIteratorProtocol https://github.com/ swiftlang/swift-evolution/blob/main/proposals/0421-generalize-async-sequence.md • AsyncSequence | Apple Developer Documentation https://developer.apple.com/documentation/Swift/ AsyncSequence • Sequence | Apple Developer Documentation https://developer.apple.com/documentation/swift/sequence • SE - 0297 : Concurrency Interoperability with Objective-C https://github.com/swiftlang/swift-evolution/blob/main/ proposals/0297-concurrency-objc.md • SE - 0413 : Typed throws https://github.com/swiftlang/swift-evolution/blob/main/proposals/0413-typed- throws.md • SE - 0414 : Region based Isolation https://github.com/swiftlang/swift-evolution/blob/main/proposals/0414-region- based-isolation.md 136
  119. Copyright © 2024 treastrain / Tanaka RyogaɹAll rights reserved. #iosdc

    #a ࢖͏ɾ࡞ΔɾଞͷσβΠϯύλʔϯʢΫϩʔδϟɺDelegate ͳͲʣ͔ΒҠߦ͢Δ Mastering AsyncSequence 137 iOSDC Japan 2024 Day 2 #iosdc #a Happy async iterating! ࢿྉ͸ tret.jp ʹܝࡌ ⏩