swift-code-reviewer-skill 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +44 -162
  2. package/README.md +91 -21
  3. package/SKILL.md +107 -725
  4. package/bin/install.js +87 -22
  5. package/package.json +16 -2
  6. package/references/companion-skills.md +70 -0
  7. package/skills/README.md +43 -0
  8. package/skills/swift-concurrency/NOTICE.md +18 -0
  9. package/skills/swift-concurrency/SKILL.md +235 -0
  10. package/skills/swift-concurrency/references/actors.md +640 -0
  11. package/skills/swift-concurrency/references/async-await-basics.md +249 -0
  12. package/skills/swift-concurrency/references/async-sequences.md +635 -0
  13. package/skills/swift-concurrency/references/core-data.md +533 -0
  14. package/skills/swift-concurrency/references/glossary.md +96 -0
  15. package/skills/swift-concurrency/references/linting.md +38 -0
  16. package/skills/swift-concurrency/references/memory-management.md +542 -0
  17. package/skills/swift-concurrency/references/migration.md +721 -0
  18. package/skills/swift-concurrency/references/performance.md +574 -0
  19. package/skills/swift-concurrency/references/sendable.md +578 -0
  20. package/skills/swift-concurrency/references/tasks.md +604 -0
  21. package/skills/swift-concurrency/references/testing.md +565 -0
  22. package/skills/swift-concurrency/references/threading.md +452 -0
  23. package/skills/swift-expert/NOTICE.md +18 -0
  24. package/skills/swift-expert/SKILL.md +226 -0
  25. package/skills/swift-expert/references/async-concurrency.md +363 -0
  26. package/skills/swift-expert/references/memory-performance.md +380 -0
  27. package/skills/swift-expert/references/protocol-oriented.md +357 -0
  28. package/skills/swift-expert/references/swiftui-patterns.md +294 -0
  29. package/skills/swift-expert/references/testing-patterns.md +402 -0
  30. package/skills/swift-testing/NOTICE.md +18 -0
  31. package/skills/swift-testing/SKILL.md +295 -0
  32. package/skills/swift-testing/references/async-testing.md +245 -0
  33. package/skills/swift-testing/references/dump-snapshot-testing.md +265 -0
  34. package/skills/swift-testing/references/fixtures.md +193 -0
  35. package/skills/swift-testing/references/integration-testing.md +189 -0
  36. package/skills/swift-testing/references/migration-xctest.md +301 -0
  37. package/skills/swift-testing/references/parameterized-tests.md +171 -0
  38. package/skills/swift-testing/references/snapshot-testing.md +201 -0
  39. package/skills/swift-testing/references/test-doubles.md +243 -0
  40. package/skills/swift-testing/references/test-organization.md +231 -0
  41. package/skills/swiftui-expert-skill/NOTICE.md +18 -0
  42. package/skills/swiftui-expert-skill/SKILL.md +281 -0
  43. package/skills/swiftui-expert-skill/references/accessibility-patterns.md +151 -0
  44. package/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
  45. package/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
  46. package/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
  47. package/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  48. package/skills/swiftui-expert-skill/references/charts.md +602 -0
  49. package/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
  50. package/skills/swiftui-expert-skill/references/latest-apis.md +464 -0
  51. package/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  52. package/skills/swiftui-expert-skill/references/liquid-glass.md +414 -0
  53. package/skills/swiftui-expert-skill/references/list-patterns.md +394 -0
  54. package/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
  55. package/skills/swiftui-expert-skill/references/macos-views.md +357 -0
  56. package/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  57. package/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
  58. package/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  59. package/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  60. package/skills/swiftui-expert-skill/references/state-management.md +417 -0
  61. package/skills/swiftui-expert-skill/references/view-structure.md +389 -0
  62. package/skills/swiftui-ui-patterns/NOTICE.md +18 -0
  63. package/skills/swiftui-ui-patterns/SKILL.md +95 -0
  64. package/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
  65. package/skills/swiftui-ui-patterns/references/async-state.md +96 -0
  66. package/skills/swiftui-ui-patterns/references/components-index.md +50 -0
  67. package/skills/swiftui-ui-patterns/references/controls.md +57 -0
  68. package/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
  69. package/skills/swiftui-ui-patterns/references/focus.md +90 -0
  70. package/skills/swiftui-ui-patterns/references/form.md +97 -0
  71. package/skills/swiftui-ui-patterns/references/grids.md +71 -0
  72. package/skills/swiftui-ui-patterns/references/haptics.md +71 -0
  73. package/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  74. package/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  75. package/skills/swiftui-ui-patterns/references/list.md +86 -0
  76. package/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  77. package/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
  78. package/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  79. package/skills/swiftui-ui-patterns/references/media.md +73 -0
  80. package/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
  81. package/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
  82. package/skills/swiftui-ui-patterns/references/overlay.md +45 -0
  83. package/skills/swiftui-ui-patterns/references/performance.md +62 -0
  84. package/skills/swiftui-ui-patterns/references/previews.md +48 -0
  85. package/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  86. package/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
  87. package/skills/swiftui-ui-patterns/references/searchable.md +71 -0
  88. package/skills/swiftui-ui-patterns/references/sheets.md +155 -0
  89. package/skills/swiftui-ui-patterns/references/split-views.md +72 -0
  90. package/skills/swiftui-ui-patterns/references/tabview.md +114 -0
  91. package/skills/swiftui-ui-patterns/references/theming.md +71 -0
  92. package/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
  93. package/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
  94. package/templates/agents/swift-code-reviewer.md +78 -0
  95. package/templates/commands/review.md +56 -0
@@ -0,0 +1,635 @@
1
+ # Async Sequences and Streams
2
+
3
+ Patterns for iterating over values that arrive over time.
4
+
5
+ ## AsyncSequence
6
+
7
+ Protocol for asynchronous iteration over values that become available over time.
8
+
9
+ ### Basic usage
10
+
11
+ ```swift
12
+ for await value in someAsyncSequence {
13
+ print(value)
14
+ }
15
+ ```
16
+
17
+ **Key difference from Sequence**: Values may not all be available immediately.
18
+
19
+ ### Custom implementation
20
+
21
+ ```swift
22
+ struct Counter: AsyncSequence, AsyncIteratorProtocol {
23
+ typealias Element = Int
24
+
25
+ let limit: Int
26
+ var current = 1
27
+
28
+ mutating func next() async -> Int? {
29
+ guard !Task.isCancelled else { return nil }
30
+ guard current <= limit else { return nil }
31
+
32
+ let result = current
33
+ current += 1
34
+ return result
35
+ }
36
+
37
+ func makeAsyncIterator() -> Counter {
38
+ self
39
+ }
40
+ }
41
+
42
+ // Usage
43
+ for await count in Counter(limit: 5) {
44
+ print(count) // 1, 2, 3, 4, 5
45
+ }
46
+ ```
47
+
48
+ ### Standard operators
49
+
50
+ Same functional operators as regular sequences:
51
+
52
+ ```swift
53
+ // Filter
54
+ for await even in Counter(limit: 5).filter({ $0 % 2 == 0 }) {
55
+ print(even) // 2, 4
56
+ }
57
+
58
+ // Map
59
+ let mapped = Counter(limit: 5).map { $0 % 2 == 0 ? "Even" : "Odd" }
60
+ for await label in mapped {
61
+ print(label)
62
+ }
63
+
64
+ // Contains (awaits until found or sequence ends)
65
+ let contains = await Counter(limit: 5).contains(3) // true
66
+ ```
67
+
68
+ ### Termination
69
+
70
+ Return `nil` from `next()` to end iteration:
71
+
72
+ ```swift
73
+ mutating func next() async -> Int? {
74
+ guard !Task.isCancelled else {
75
+ return nil // Stop on cancellation
76
+ }
77
+
78
+ guard current <= limit else {
79
+ return nil // Stop at limit
80
+ }
81
+
82
+ return current
83
+ }
84
+ ```
85
+
86
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 6.1: Working with asynchronous sequences](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
87
+
88
+ ## AsyncStream
89
+
90
+ Convenient way to create async sequences without implementing protocols.
91
+
92
+ ### Basic creation
93
+
94
+ ```swift
95
+ let stream = AsyncStream<Int> { continuation in
96
+ for i in 1...5 {
97
+ continuation.yield(i)
98
+ }
99
+ continuation.finish()
100
+ }
101
+
102
+ for await value in stream {
103
+ print(value)
104
+ }
105
+ ```
106
+
107
+ ### AsyncThrowingStream
108
+
109
+ For streams that can fail:
110
+
111
+ ```swift
112
+ let throwingStream = AsyncThrowingStream<Int, Error> { continuation in
113
+ continuation.yield(1)
114
+ continuation.yield(2)
115
+ continuation.finish(throwing: SomeError())
116
+ }
117
+
118
+ do {
119
+ for try await value in throwingStream {
120
+ print(value)
121
+ }
122
+ } catch {
123
+ print("Error: \(error)")
124
+ }
125
+ ```
126
+
127
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 6.2: Using AsyncStream and AsyncThrowingStream in your code](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
128
+
129
+ ## Bridging Closures to Streams
130
+
131
+ ### Progress + completion handlers
132
+
133
+ ```swift
134
+ // Old closure-based API
135
+ struct FileDownloader {
136
+ enum Status {
137
+ case downloading(Float)
138
+ case finished(Data)
139
+ }
140
+
141
+ func download(
142
+ _ url: URL,
143
+ progressHandler: @escaping (Float) -> Void,
144
+ completion: @escaping (Result<Data, Error>) -> Void
145
+ ) throws {
146
+ // Implementation
147
+ }
148
+ }
149
+
150
+ // Modern stream-based API
151
+ extension FileDownloader {
152
+ func download(_ url: URL) -> AsyncThrowingStream<Status, Error> {
153
+ AsyncThrowingStream { continuation in
154
+ do {
155
+ try self.download(url, progressHandler: { progress in
156
+ continuation.yield(.downloading(progress))
157
+ }, completion: { result in
158
+ switch result {
159
+ case .success(let data):
160
+ continuation.yield(.finished(data))
161
+ continuation.finish()
162
+ case .failure(let error):
163
+ continuation.finish(throwing: error)
164
+ }
165
+ })
166
+ } catch {
167
+ continuation.finish(throwing: error)
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ // Usage
174
+ for try await status in downloader.download(url) {
175
+ switch status {
176
+ case .downloading(let progress):
177
+ print("Progress: \(progress)")
178
+ case .finished(let data):
179
+ print("Done: \(data.count) bytes")
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### Simplified with Result
185
+
186
+ ```swift
187
+ AsyncThrowingStream { continuation in
188
+ try self.download(url, progressHandler: { progress in
189
+ continuation.yield(.downloading(progress))
190
+ }, completion: { result in
191
+ continuation.yield(with: result.map { .finished($0) })
192
+ continuation.finish()
193
+ })
194
+ }
195
+ ```
196
+
197
+ ## Bridging Delegates
198
+
199
+ ### Location updates example
200
+
201
+ ```swift
202
+ final class LocationMonitor: NSObject {
203
+ private var continuation: AsyncThrowingStream<CLLocation, Error>.Continuation?
204
+ let stream: AsyncThrowingStream<CLLocation, Error>
205
+
206
+ override init() {
207
+ var capturedContinuation: AsyncThrowingStream<CLLocation, Error>.Continuation?
208
+ stream = AsyncThrowingStream { continuation in
209
+ capturedContinuation = continuation
210
+ }
211
+ super.init()
212
+ self.continuation = capturedContinuation
213
+
214
+ locationManager.delegate = self
215
+ locationManager.startUpdatingLocation()
216
+ }
217
+ }
218
+
219
+ extension LocationMonitor: CLLocationManagerDelegate {
220
+ func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
221
+ for location in locations {
222
+ continuation?.yield(location)
223
+ }
224
+ }
225
+
226
+ func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
227
+ continuation?.finish(throwing: error)
228
+ }
229
+ }
230
+
231
+ // Usage
232
+ let monitor = LocationMonitor()
233
+ for try await location in monitor.stream {
234
+ print("Location: \(location.coordinate)")
235
+ }
236
+ ```
237
+
238
+ ## Stream Lifecycle
239
+
240
+ ### Termination callback
241
+
242
+ ```swift
243
+ AsyncThrowingStream<Int, Error> { continuation in
244
+ continuation.onTermination = { @Sendable reason in
245
+ print("Terminated: \(reason)")
246
+ // Cleanup: remove observers, cancel work, etc.
247
+ }
248
+
249
+ continuation.yield(1)
250
+ continuation.finish()
251
+ }
252
+ ```
253
+
254
+ **Termination reasons**:
255
+ - `.finished` - Normal completion
256
+ - `.finished(Error?)` - Completed with error (throwing stream)
257
+ - `.cancelled` - Task canceled
258
+
259
+ ### Cancellation
260
+
261
+ Streams cancel when:
262
+ - Enclosing task cancels
263
+ - Stream goes out of scope
264
+
265
+ ```swift
266
+ let task = Task {
267
+ for try await status in download(url) {
268
+ print(status)
269
+ }
270
+ }
271
+
272
+ task.cancel() // Triggers onTermination with .cancelled
273
+ ```
274
+
275
+ **No explicit cancel method** - rely on task cancellation.
276
+
277
+ ## Buffer Policies
278
+
279
+ Control what happens to values when no one is awaiting:
280
+
281
+ ### .unbounded (default)
282
+
283
+ Buffers all values until consumed:
284
+
285
+ ```swift
286
+ let stream = AsyncStream<Int> { continuation in
287
+ (0...5).forEach { continuation.yield($0) }
288
+ continuation.finish()
289
+ }
290
+
291
+ try await Task.sleep(for: .seconds(1))
292
+
293
+ for await value in stream {
294
+ print(value) // Prints all: 0, 1, 2, 3, 4, 5
295
+ }
296
+ ```
297
+
298
+ ### .bufferingNewest(n)
299
+
300
+ Keeps only the newest N values:
301
+
302
+ ```swift
303
+ let stream = AsyncStream(bufferingPolicy: .bufferingNewest(1)) { continuation in
304
+ (0...5).forEach { continuation.yield($0) }
305
+ continuation.finish()
306
+ }
307
+
308
+ try await Task.sleep(for: .seconds(1))
309
+
310
+ for await value in stream {
311
+ print(value) // Prints only: 5
312
+ }
313
+ ```
314
+
315
+ ### .bufferingOldest(n)
316
+
317
+ Keeps only the oldest N values:
318
+
319
+ ```swift
320
+ let stream = AsyncStream(bufferingPolicy: .bufferingOldest(1)) { continuation in
321
+ (0...5).forEach { continuation.yield($0) }
322
+ continuation.finish()
323
+ }
324
+
325
+ try await Task.sleep(for: .seconds(1))
326
+
327
+ for await value in stream {
328
+ print(value) // Prints only: 0
329
+ }
330
+ ```
331
+
332
+ ### .bufferingNewest(0)
333
+
334
+ Only receives values emitted after iteration starts:
335
+
336
+ ```swift
337
+ let stream = AsyncStream(bufferingPolicy: .bufferingNewest(0)) { continuation in
338
+ continuation.yield(1) // Discarded
339
+
340
+ Task {
341
+ try await Task.sleep(for: .seconds(2))
342
+ continuation.yield(2) // Received
343
+ continuation.finish()
344
+ }
345
+ }
346
+
347
+ try await Task.sleep(for: .seconds(1))
348
+
349
+ for await value in stream {
350
+ print(value) // Prints only: 2
351
+ }
352
+ ```
353
+
354
+ **Use case**: Location updates, file system changes - only care about latest.
355
+
356
+ ## Repeated Async Calls
357
+
358
+ Use `init(unfolding:onCancel:)` for polling:
359
+
360
+ ```swift
361
+ struct PingService {
362
+ func startPinging() -> AsyncStream<Bool> {
363
+ AsyncStream {
364
+ try? await Task.sleep(for: .seconds(5))
365
+ return await ping()
366
+ } onCancel: {
367
+ print("Pinging cancelled")
368
+ }
369
+ }
370
+
371
+ func ping() async -> Bool {
372
+ // Network request
373
+ return true
374
+ }
375
+ }
376
+
377
+ // Usage
378
+ for await result in pingService.startPinging() {
379
+ print("Ping: \(result)")
380
+ }
381
+ ```
382
+
383
+ ## Standard Library Integration
384
+
385
+ ### NotificationCenter
386
+
387
+ ```swift
388
+ let stream = NotificationCenter.default.notifications(
389
+ named: .NSSystemTimeZoneDidChange
390
+ )
391
+
392
+ for await notification in stream {
393
+ print("Time zone changed")
394
+ }
395
+ ```
396
+
397
+ ### Combine publishers
398
+
399
+ ```swift
400
+ let numbers = [1, 2, 3, 4, 5]
401
+ let filtered = numbers.publisher.filter { $0 % 2 == 0 }
402
+
403
+ for await number in filtered.values {
404
+ print(number) // 2, 4
405
+ }
406
+ ```
407
+
408
+ ### Task groups
409
+
410
+ ```swift
411
+ await withTaskGroup(of: Image.self) { group in
412
+ for url in urls {
413
+ group.addTask { await download(url) }
414
+ }
415
+
416
+ for await image in group {
417
+ display(image)
418
+ }
419
+ }
420
+ ```
421
+
422
+ ## Limitations
423
+
424
+ ### Single consumer only
425
+
426
+ Unlike Combine, streams support one consumer at a time:
427
+
428
+ ```swift
429
+ let stream = AsyncStream { continuation in
430
+ (0...5).forEach { continuation.yield($0) }
431
+ continuation.finish()
432
+ }
433
+
434
+ Task {
435
+ for await value in stream {
436
+ print("Consumer 1: \(value)")
437
+ }
438
+ }
439
+
440
+ Task {
441
+ for await value in stream {
442
+ print("Consumer 2: \(value)")
443
+ }
444
+ }
445
+
446
+ // Unpredictable output - values split between consumers
447
+ // Consumer 1: 0
448
+ // Consumer 2: 1
449
+ // Consumer 1: 2
450
+ // Consumer 2: 3
451
+ ```
452
+
453
+ **Solution**: Create separate streams or use third-party libraries (AsyncExtensions).
454
+
455
+ ### No values after termination
456
+
457
+ Once finished, stream won't emit new values:
458
+
459
+ ```swift
460
+ let stream = AsyncStream<Int> { continuation in
461
+ continuation.finish() // Terminate immediately
462
+ continuation.yield(1) // Never received
463
+ }
464
+
465
+ for await value in stream {
466
+ print(value) // Loop exits immediately
467
+ }
468
+ ```
469
+
470
+ ## Decision Guide
471
+
472
+ ### Use AsyncSequence when:
473
+
474
+ - Implementing standard library-style protocols
475
+ - Need fine-grained control over iteration
476
+ - Building reusable sequence types
477
+ - Working with existing sequence protocols
478
+
479
+ **Reality**: Rarely needed in application code.
480
+
481
+ ### Use AsyncStream when:
482
+
483
+ - Bridging delegates to async/await
484
+ - Converting closure-based APIs
485
+ - Emitting events manually
486
+ - Polling or repeated async operations
487
+ - Most common use case
488
+
489
+ ### Use regular async methods when:
490
+
491
+ - Single value returned
492
+ - No progress updates needed
493
+ - Simple request/response pattern
494
+
495
+ ```swift
496
+ // Use this
497
+ func fetchData() async throws -> Data
498
+
499
+ // Not this
500
+ func fetchData() -> AsyncThrowingStream<Data, Error>
501
+
502
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 6.3: Deciding between AsyncSequence, AsyncStream, or regular asynchronous methods](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
503
+ ```
504
+
505
+ ## Common Patterns
506
+
507
+ ### Progress reporting
508
+
509
+ ```swift
510
+ func download(_ url: URL) -> AsyncThrowingStream<DownloadEvent, Error> {
511
+ AsyncThrowingStream { continuation in
512
+ Task {
513
+ do {
514
+ var progress: Double = 0
515
+ while progress < 1.0 {
516
+ progress += 0.1
517
+ continuation.yield(.progress(progress))
518
+ try await Task.sleep(for: .milliseconds(100))
519
+ }
520
+
521
+ let data = try await URLSession.shared.data(from: url).0
522
+ continuation.yield(.completed(data))
523
+ continuation.finish()
524
+ } catch {
525
+ continuation.finish(throwing: error)
526
+ }
527
+ }
528
+ }
529
+ }
530
+ ```
531
+
532
+ ### Monitoring file system
533
+
534
+ ```swift
535
+ func watchDirectory(_ path: String) -> AsyncStream<FileEvent> {
536
+ AsyncStream(bufferingPolicy: .bufferingNewest(1)) { continuation in
537
+ let source = DispatchSource.makeFileSystemObjectSource(
538
+ fileDescriptor: fd,
539
+ eventMask: .write,
540
+ queue: .main
541
+ )
542
+
543
+ source.setEventHandler {
544
+ continuation.yield(.fileChanged(path))
545
+ }
546
+
547
+ continuation.onTermination = { _ in
548
+ source.cancel()
549
+ }
550
+
551
+ source.resume()
552
+ }
553
+ }
554
+ ```
555
+
556
+ ### Timer/polling
557
+
558
+ ```swift
559
+ func timer(interval: Duration) -> AsyncStream<Date> {
560
+ AsyncStream { continuation in
561
+ Task {
562
+ while !Task.isCancelled {
563
+ continuation.yield(Date())
564
+ try? await Task.sleep(for: interval)
565
+ }
566
+ continuation.finish()
567
+ }
568
+ }
569
+ }
570
+
571
+ // Usage
572
+ for await date in timer(interval: .seconds(1)) {
573
+ print("Tick: \(date)")
574
+ }
575
+ ```
576
+
577
+ ## Best Practices
578
+
579
+ 1. **Always call finish()** - Streams stay alive until terminated
580
+ 2. **Use buffer policies wisely** - Match your use case (latest value vs all values)
581
+ 3. **Handle cancellation** - Set `onTermination` for cleanup
582
+ 4. **Single consumer** - Don't share streams across multiple consumers
583
+ 5. **Prefer streams over closures** - More composable and cancellable
584
+ 6. **Check Task.isCancelled** - Respect cancellation in custom sequences
585
+ 7. **Use throwing variant** - When operations can fail
586
+ 8. **Consider regular async** - If only returning single value
587
+
588
+ ## Debugging
589
+
590
+ ### Add termination logging
591
+
592
+ ```swift
593
+ continuation.onTermination = { reason in
594
+ print("Stream ended: \(reason)")
595
+ }
596
+ ```
597
+
598
+ ### Validate finish() calls
599
+
600
+ ```swift
601
+ // ❌ Forgot to finish
602
+ AsyncStream { continuation in
603
+ continuation.yield(1)
604
+ // Stream never ends!
605
+ }
606
+
607
+ // ✅ Always finish
608
+ AsyncStream { continuation in
609
+ continuation.yield(1)
610
+ continuation.finish()
611
+ }
612
+ ```
613
+
614
+ ### Check for dropped values
615
+
616
+ ```swift
617
+ let stream = AsyncStream(bufferingPolicy: .bufferingNewest(1)) { continuation in
618
+ for i in 1...100 {
619
+ continuation.yield(i)
620
+ print("Yielded: \(i)")
621
+ }
622
+ continuation.finish()
623
+ }
624
+
625
+ // If consumer is slow, many values dropped
626
+ for await value in stream {
627
+ print("Received: \(value)")
628
+ try? await Task.sleep(for: .seconds(1))
629
+ }
630
+ ```
631
+
632
+ ## Further Learning
633
+
634
+ For real-world migration examples, performance patterns, and advanced stream techniques, see [Swift Concurrency Course](https://www.swiftconcurrencycourse.com).
635
+