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,542 @@
1
+ # Memory Management
2
+
3
+ Preventing retain cycles and managing object lifetimes in Swift Concurrency.
4
+
5
+ ## Core Concepts
6
+
7
+ ### Tasks capture like closures
8
+
9
+ Tasks capture variables and references just like regular closures. Swift doesn't automatically prevent retain cycles in concurrent code.
10
+
11
+ ```swift
12
+ Task {
13
+ self.doWork() // ⚠️ Strong capture of self
14
+ }
15
+ ```
16
+
17
+ ### Why concurrency hides memory issues
18
+
19
+ - Tasks may live longer than expected
20
+ - Async operations delay execution
21
+ - Harder to track when memory should be released
22
+ - Long-running tasks can hold references indefinitely
23
+
24
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 8.1: Overview of memory management in Swift Concurrency](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
25
+
26
+ ## Retain Cycles
27
+
28
+ ### What is a retain cycle?
29
+
30
+ Two or more objects hold strong references to each other, preventing deallocation.
31
+
32
+ ```swift
33
+ class A {
34
+ var b: B?
35
+ }
36
+
37
+ class B {
38
+ var a: A?
39
+ }
40
+
41
+ let a = A()
42
+ let b = B()
43
+ a.b = b
44
+ b.a = a // Retain cycle - neither can be deallocated
45
+ ```
46
+
47
+ ### Retain cycles with Tasks
48
+
49
+ When task captures `self` strongly and `self` owns the task:
50
+
51
+ ```swift
52
+ @MainActor
53
+ final class ImageLoader {
54
+ var task: Task<Void, Never>?
55
+
56
+ func startPolling() {
57
+ task = Task {
58
+ while true {
59
+ self.pollImages() // ⚠️ Strong capture
60
+ try? await Task.sleep(for: .seconds(1))
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ var loader: ImageLoader? = .init()
67
+ loader?.startPolling()
68
+ loader = nil // ⚠️ Loader never deallocated - retain cycle!
69
+ ```
70
+
71
+ **Problem**: Task holds `self`, `self` holds task → neither released.
72
+
73
+ ## Breaking Retain Cycles
74
+
75
+ ### Use weak self
76
+
77
+ ```swift
78
+ func startPolling() {
79
+ task = Task { [weak self] in
80
+ while let self = self {
81
+ self.pollImages()
82
+ try? await Task.sleep(for: .seconds(1))
83
+ }
84
+ }
85
+ }
86
+
87
+ var loader: ImageLoader? = .init()
88
+ loader?.startPolling()
89
+ loader = nil // ✅ Loader deallocated, task stops
90
+ ```
91
+
92
+ ### Pattern for long-running tasks
93
+
94
+ ```swift
95
+ task = Task { [weak self] in
96
+ while let self = self {
97
+ await self.doWork()
98
+ try? await Task.sleep(for: interval)
99
+ }
100
+ }
101
+ ```
102
+
103
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 8.2: Preventing retain cycles when using Tasks](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
104
+
105
+ Loop exits when `self` becomes `nil`.
106
+
107
+ ## One-Way Retention
108
+
109
+ Task retains `self`, but `self` doesn't retain task. Object stays alive until task completes.
110
+
111
+ ```swift
112
+ @MainActor
113
+ final class ViewModel {
114
+ func fetchData() {
115
+ Task {
116
+ await performRequest()
117
+ updateUI() // ⚠️ Strong capture
118
+ }
119
+ }
120
+ }
121
+
122
+ var viewModel: ViewModel? = .init()
123
+ viewModel?.fetchData()
124
+ viewModel = nil // ViewModel stays alive until task completes
125
+ ```
126
+
127
+ **Execution order**:
128
+ 1. Task starts
129
+ 2. `viewModel = nil` (but object not deallocated)
130
+ 3. Task completes
131
+ 4. ViewModel finally deallocated
132
+
133
+ ### When one-way retention is acceptable
134
+
135
+ Short-lived tasks that complete quickly:
136
+
137
+ ```swift
138
+ func saveData() {
139
+ Task {
140
+ await database.save(self.data) // OK - completes quickly
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### When to use weak self
146
+
147
+ Long-running or indefinite tasks:
148
+
149
+ ```swift
150
+ func startMonitoring() {
151
+ Task { [weak self] in
152
+ for await event in eventStream {
153
+ self?.handle(event)
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ ## Async Sequences and Retention
160
+
161
+ ### Problem: Infinite sequences
162
+
163
+ ```swift
164
+ @MainActor
165
+ final class AppLifecycleViewModel {
166
+ private(set) var isActive = false
167
+ private var task: Task<Void, Never>?
168
+
169
+ func startObserving() {
170
+ task = Task {
171
+ for await _ in NotificationCenter.default.notifications(
172
+ named: .didBecomeActive
173
+ ) {
174
+ isActive = true // ⚠️ Strong capture, never ends
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ var viewModel: AppLifecycleViewModel? = .init()
181
+ viewModel?.startObserving()
182
+ viewModel = nil // ⚠️ Never deallocated - sequence continues
183
+ ```
184
+
185
+ **Problem**: Async sequence never finishes, task holds `self` indefinitely.
186
+
187
+ ### Solution 1: Manual cancellation
188
+
189
+ ```swift
190
+ func startObserving() {
191
+ task = Task {
192
+ for await _ in NotificationCenter.default.notifications(
193
+ named: .didBecomeActive
194
+ ) {
195
+ isActive = true
196
+ }
197
+ }
198
+ }
199
+
200
+ func stopObserving() {
201
+ task?.cancel()
202
+ }
203
+
204
+ // Usage
205
+ viewModel?.startObserving()
206
+ viewModel?.stopObserving() // Must call before release
207
+ viewModel = nil
208
+ ```
209
+
210
+ ### Solution 2: Weak self with guard
211
+
212
+ ```swift
213
+ func startObserving() {
214
+ task = Task { [weak self] in
215
+ for await _ in NotificationCenter.default.notifications(
216
+ named: .didBecomeActive
217
+ ) {
218
+ guard let self = self else { return }
219
+ self.isActive = true
220
+ }
221
+ }
222
+ }
223
+ ```
224
+
225
+ Task exits when `self` deallocates.
226
+
227
+ ## Isolated deinit (Swift 6.2+)
228
+
229
+ Clean up actor-isolated state in deinit:
230
+
231
+ ```swift
232
+ @MainActor
233
+ final class ViewModel {
234
+ private var task: Task<Void, Never>?
235
+
236
+ isolated deinit {
237
+ task?.cancel()
238
+ }
239
+ }
240
+ ```
241
+
242
+ **Limitation**: Won't break retain cycles (deinit never called if cycle exists).
243
+
244
+ **Use for**: Cleanup when object is being deallocated normally.
245
+
246
+ ## Common Patterns
247
+
248
+ ### Short-lived task (strong capture OK)
249
+
250
+ ```swift
251
+ func saveData() {
252
+ Task {
253
+ await database.save(self.data)
254
+ self.updateUI()
255
+ }
256
+ }
257
+ ```
258
+
259
+ **When safe**: Task completes quickly, acceptable for object to live until done.
260
+
261
+ ### Long-running task (weak self required)
262
+
263
+ ```swift
264
+ func startPolling() {
265
+ task = Task { [weak self] in
266
+ while let self = self {
267
+ await self.fetchUpdates()
268
+ try? await Task.sleep(for: .seconds(5))
269
+ }
270
+ }
271
+ }
272
+ ```
273
+
274
+ ### Async sequence monitoring (weak self + guard)
275
+
276
+ ```swift
277
+ func startMonitoring() {
278
+ task = Task { [weak self] in
279
+ for await event in eventStream {
280
+ guard let self = self else { return }
281
+ self.handle(event)
282
+ }
283
+ }
284
+ }
285
+ ```
286
+
287
+ ### Cancellable work with cleanup
288
+
289
+ ```swift
290
+ func startWork() {
291
+ task = Task { [weak self] in
292
+ defer { self?.cleanup() }
293
+
294
+ while let self = self {
295
+ await self.doWork()
296
+ try? await Task.sleep(for: .seconds(1))
297
+ }
298
+ }
299
+ }
300
+ ```
301
+
302
+ ## Detection Strategies
303
+
304
+ ### Add deinit logging
305
+
306
+ ```swift
307
+ deinit {
308
+ print("✅ \(type(of: self)) deallocated")
309
+ }
310
+ ```
311
+
312
+ If deinit never prints → likely retain cycle.
313
+
314
+ ### Memory graph debugger
315
+
316
+ 1. Run app in Xcode
317
+ 2. Debug → Debug Memory Graph
318
+ 3. Look for cycles in object graph
319
+
320
+ ### Instruments
321
+
322
+ Use Leaks instrument to detect retain cycles at runtime.
323
+
324
+ ## Decision Tree
325
+
326
+ ```
327
+ Task captures self?
328
+ ├─ Task completes quickly?
329
+ │ └─ Strong capture OK
330
+
331
+ ├─ Long-running or infinite?
332
+ │ ├─ Can use weak self? → Use [weak self]
333
+ │ ├─ Need manual control? → Store task, cancel explicitly
334
+ │ └─ Async sequence? → [weak self] + guard
335
+
336
+ └─ Self owns task?
337
+ ├─ Yes → High risk of retain cycle
338
+ └─ No → Lower risk, but check lifetime
339
+ ```
340
+
341
+ ## Best Practices
342
+
343
+ 1. **Default to weak self** for long-running tasks
344
+ 2. **Use guard let self** in async sequences
345
+ 3. **Cancel tasks explicitly** when possible
346
+ 4. **Add deinit logging** during development
347
+ 5. **Test object deallocation** in unit tests
348
+ 6. **Use Memory Graph** to verify no cycles
349
+ 7. **Document lifetime expectations** in comments
350
+ 8. **Prefer cancellation** over weak self when possible
351
+ 9. **Avoid nested strong captures** in task closures
352
+ 10. **Use isolated deinit** for cleanup (Swift 6.2+)
353
+
354
+ ## Testing for Leaks
355
+
356
+ ### Unit test pattern
357
+
358
+ ```swift
359
+ func testViewModelDeallocates() async {
360
+ var viewModel: ViewModel? = ViewModel()
361
+ weak var weakViewModel = viewModel
362
+
363
+ viewModel?.startWork()
364
+ viewModel = nil
365
+
366
+ // Give tasks time to complete
367
+ try? await Task.sleep(for: .milliseconds(100))
368
+
369
+ XCTAssertNil(weakViewModel, "ViewModel should be deallocated")
370
+ }
371
+ ```
372
+
373
+ ### SwiftUI view test
374
+
375
+ ```swift
376
+ func testViewDeallocates() {
377
+ var view: MyView? = MyView()
378
+ weak var weakView = view
379
+
380
+ view = nil
381
+
382
+ XCTAssertNil(weakView)
383
+ }
384
+ ```
385
+
386
+ ## Common Mistakes
387
+
388
+ ### ❌ Forgetting weak self in loops
389
+
390
+ ```swift
391
+ Task {
392
+ while true {
393
+ self.poll() // Retain cycle
394
+ try? await Task.sleep(for: .seconds(1))
395
+ }
396
+ }
397
+ ```
398
+
399
+ ### ❌ Strong capture in async sequences
400
+
401
+ ```swift
402
+ Task {
403
+ for await item in stream {
404
+ self.process(item) // May never release
405
+ }
406
+ }
407
+ ```
408
+
409
+ ### ❌ Not canceling stored tasks
410
+
411
+ ```swift
412
+ class Manager {
413
+ var task: Task<Void, Never>?
414
+
415
+ func start() {
416
+ task = Task {
417
+ await self.work() // Retain cycle
418
+ }
419
+ }
420
+
421
+ // Missing: deinit { task?.cancel() }
422
+ }
423
+ ```
424
+
425
+ ### ❌ Assuming deinit breaks cycles
426
+
427
+ ```swift
428
+ deinit {
429
+ task?.cancel() // Never called if retain cycle exists
430
+ }
431
+ ```
432
+
433
+ ## Examples by Use Case
434
+
435
+ ### Polling service
436
+
437
+ ```swift
438
+ final class PollingService {
439
+ private var task: Task<Void, Never>?
440
+
441
+ func start() {
442
+ task = Task { [weak self] in
443
+ while let self = self {
444
+ await self.poll()
445
+ try? await Task.sleep(for: .seconds(5))
446
+ }
447
+ }
448
+ }
449
+
450
+ func stop() {
451
+ task?.cancel()
452
+ }
453
+ }
454
+ ```
455
+
456
+ ### Notification observer
457
+
458
+ ```swift
459
+ @MainActor
460
+ final class NotificationObserver {
461
+ private var task: Task<Void, Never>?
462
+
463
+ func startObserving() {
464
+ task = Task { [weak self] in
465
+ for await notification in NotificationCenter.default.notifications(
466
+ named: .someNotification
467
+ ) {
468
+ guard let self = self else { return }
469
+ self.handle(notification)
470
+ }
471
+ }
472
+ }
473
+
474
+ isolated deinit {
475
+ task?.cancel()
476
+ }
477
+ }
478
+ ```
479
+
480
+ ### Download manager
481
+
482
+ ```swift
483
+ final class DownloadManager {
484
+ private var tasks: [URL: Task<Data, Error>] = [:]
485
+
486
+ func download(_ url: URL) async throws -> Data {
487
+ let task = Task { [weak self] in
488
+ defer { self?.tasks.removeValue(forKey: url) }
489
+ return try await URLSession.shared.data(from: url).0
490
+ }
491
+
492
+ tasks[url] = task
493
+ return try await task.value
494
+ }
495
+
496
+ func cancelAll() {
497
+ tasks.values.forEach { $0.cancel() }
498
+ tasks.removeAll()
499
+ }
500
+ }
501
+ ```
502
+
503
+ ### Timer
504
+
505
+ ```swift
506
+ actor Timer {
507
+ private var task: Task<Void, Never>?
508
+
509
+ func start(interval: Duration, action: @Sendable () async -> Void) {
510
+ task = Task {
511
+ while !Task.isCancelled {
512
+ await action()
513
+ try? await Task.sleep(for: interval)
514
+ }
515
+ }
516
+ }
517
+
518
+ func stop() {
519
+ task?.cancel()
520
+ }
521
+ }
522
+ ```
523
+
524
+ ## Debugging Checklist
525
+
526
+ When object won't deallocate:
527
+
528
+ - [ ] Check for strong self captures in tasks
529
+ - [ ] Verify tasks are canceled or complete
530
+ - [ ] Look for infinite loops or sequences
531
+ - [ ] Check if self owns the task
532
+ - [ ] Use Memory Graph to find cycles
533
+ - [ ] Add deinit logging to verify
534
+ - [ ] Test with weak references
535
+ - [ ] Review async sequence usage
536
+ - [ ] Check nested task captures
537
+ - [ ] Verify cleanup in deinit
538
+
539
+ ## Further Learning
540
+
541
+ For migration strategies, real-world examples, and advanced memory patterns, see [Swift Concurrency Course](https://www.swiftconcurrencycourse.com).
542
+