swift-code-reviewer-skill 1.2.0 → 1.3.0

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 +43 -169
  2. package/README.md +43 -2
  3. package/SKILL.md +194 -711
  4. package/bin/install.js +1 -1
  5. package/package.json +2 -1
  6. package/references/agent-loop-feedback.md +148 -0
  7. package/references/companion-skills.md +70 -0
  8. package/references/spec-adherence.md +157 -0
  9. package/skills/README.md +43 -0
  10. package/skills/swift-concurrency/NOTICE.md +18 -0
  11. package/skills/swift-concurrency/SKILL.md +235 -0
  12. package/skills/swift-concurrency/references/actors.md +640 -0
  13. package/skills/swift-concurrency/references/async-await-basics.md +249 -0
  14. package/skills/swift-concurrency/references/async-sequences.md +635 -0
  15. package/skills/swift-concurrency/references/core-data.md +533 -0
  16. package/skills/swift-concurrency/references/glossary.md +96 -0
  17. package/skills/swift-concurrency/references/linting.md +38 -0
  18. package/skills/swift-concurrency/references/memory-management.md +542 -0
  19. package/skills/swift-concurrency/references/migration.md +721 -0
  20. package/skills/swift-concurrency/references/performance.md +574 -0
  21. package/skills/swift-concurrency/references/sendable.md +578 -0
  22. package/skills/swift-concurrency/references/tasks.md +604 -0
  23. package/skills/swift-concurrency/references/testing.md +565 -0
  24. package/skills/swift-concurrency/references/threading.md +452 -0
  25. package/skills/swift-expert/NOTICE.md +18 -0
  26. package/skills/swift-expert/SKILL.md +226 -0
  27. package/skills/swift-expert/references/async-concurrency.md +363 -0
  28. package/skills/swift-expert/references/memory-performance.md +380 -0
  29. package/skills/swift-expert/references/protocol-oriented.md +357 -0
  30. package/skills/swift-expert/references/swiftui-patterns.md +294 -0
  31. package/skills/swift-expert/references/testing-patterns.md +402 -0
  32. package/skills/swift-testing/NOTICE.md +18 -0
  33. package/skills/swift-testing/SKILL.md +295 -0
  34. package/skills/swift-testing/references/async-testing.md +245 -0
  35. package/skills/swift-testing/references/dump-snapshot-testing.md +265 -0
  36. package/skills/swift-testing/references/fixtures.md +193 -0
  37. package/skills/swift-testing/references/integration-testing.md +189 -0
  38. package/skills/swift-testing/references/migration-xctest.md +301 -0
  39. package/skills/swift-testing/references/parameterized-tests.md +171 -0
  40. package/skills/swift-testing/references/snapshot-testing.md +201 -0
  41. package/skills/swift-testing/references/test-doubles.md +243 -0
  42. package/skills/swift-testing/references/test-organization.md +231 -0
  43. package/skills/swiftui-expert-skill/NOTICE.md +18 -0
  44. package/skills/swiftui-expert-skill/SKILL.md +281 -0
  45. package/skills/swiftui-expert-skill/references/accessibility-patterns.md +151 -0
  46. package/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
  47. package/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
  48. package/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
  49. package/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  50. package/skills/swiftui-expert-skill/references/charts.md +602 -0
  51. package/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
  52. package/skills/swiftui-expert-skill/references/latest-apis.md +464 -0
  53. package/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  54. package/skills/swiftui-expert-skill/references/liquid-glass.md +414 -0
  55. package/skills/swiftui-expert-skill/references/list-patterns.md +394 -0
  56. package/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
  57. package/skills/swiftui-expert-skill/references/macos-views.md +357 -0
  58. package/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  59. package/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
  60. package/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  61. package/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  62. package/skills/swiftui-expert-skill/references/state-management.md +417 -0
  63. package/skills/swiftui-expert-skill/references/view-structure.md +389 -0
  64. package/skills/swiftui-ui-patterns/NOTICE.md +18 -0
  65. package/skills/swiftui-ui-patterns/SKILL.md +95 -0
  66. package/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
  67. package/skills/swiftui-ui-patterns/references/async-state.md +96 -0
  68. package/skills/swiftui-ui-patterns/references/components-index.md +50 -0
  69. package/skills/swiftui-ui-patterns/references/controls.md +57 -0
  70. package/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
  71. package/skills/swiftui-ui-patterns/references/focus.md +90 -0
  72. package/skills/swiftui-ui-patterns/references/form.md +97 -0
  73. package/skills/swiftui-ui-patterns/references/grids.md +71 -0
  74. package/skills/swiftui-ui-patterns/references/haptics.md +71 -0
  75. package/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  76. package/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  77. package/skills/swiftui-ui-patterns/references/list.md +86 -0
  78. package/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  79. package/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
  80. package/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  81. package/skills/swiftui-ui-patterns/references/media.md +73 -0
  82. package/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
  83. package/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
  84. package/skills/swiftui-ui-patterns/references/overlay.md +45 -0
  85. package/skills/swiftui-ui-patterns/references/performance.md +62 -0
  86. package/skills/swiftui-ui-patterns/references/previews.md +48 -0
  87. package/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  88. package/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
  89. package/skills/swiftui-ui-patterns/references/searchable.md +71 -0
  90. package/skills/swiftui-ui-patterns/references/sheets.md +155 -0
  91. package/skills/swiftui-ui-patterns/references/split-views.md +72 -0
  92. package/skills/swiftui-ui-patterns/references/tabview.md +114 -0
  93. package/skills/swiftui-ui-patterns/references/theming.md +71 -0
  94. package/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
  95. package/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
@@ -0,0 +1,452 @@
1
+ # Threading
2
+
3
+ Understanding how Swift Concurrency manages threads and execution contexts.
4
+
5
+ ## Core Concepts
6
+
7
+ ### What is a Thread?
8
+
9
+ System-level resource that runs instructions. High overhead for creation and switching. Swift Concurrency abstracts thread management away.
10
+
11
+ ### Tasks vs Threads
12
+
13
+ **Tasks** are units of async work, not tied to specific threads. Swift dynamically schedules tasks on available threads from a cooperative pool.
14
+
15
+ **Key insight**: No direct relationship between one task and one thread.
16
+
17
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 7.1: How Threads relate to Tasks](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
18
+
19
+ **Important (Swift 6+)**: Avoid using `Thread.current` inside async contexts. In Swift 6 language mode, `Thread.current` is unavailable from asynchronous contexts and will fail to compile. Prefer reasoning in terms of isolation domains; use Instruments and the debugger to observe execution when needed.
20
+
21
+ ## Cooperative Thread Pool
22
+
23
+ Swift creates only as many threads as CPU cores. Tasks share these threads efficiently.
24
+
25
+ ### How it works
26
+
27
+ 1. **Limited threads**: Number matches CPU cores
28
+ 2. **Task scheduling**: Tasks scheduled onto available threads
29
+ 3. **Suspension**: At `await`, task suspends, thread freed for other work
30
+ 4. **Resumption**: Task resumes on any available thread (not necessarily the same one)
31
+
32
+ ```swift
33
+ func example() async {
34
+ print("Started on: \(Thread.current)")
35
+
36
+ try await Task.sleep(for: .seconds(1))
37
+
38
+ print("Resumed on: \(Thread.current)") // Likely different thread
39
+ }
40
+ ```
41
+
42
+ ### Benefits over GCD
43
+
44
+ **Prevents thread explosion**:
45
+ - No excessive thread creation
46
+ - No high memory overhead from idle threads
47
+ - No excessive context switching
48
+ - No priority inversion
49
+
50
+ **Better performance**:
51
+ - Fewer threads = less context switching
52
+ - Continuations instead of blocking
53
+ - CPU cores stay busy efficiently
54
+
55
+ ## Threading Mindset → Isolation Mindset
56
+
57
+ ### Old way (GCD)
58
+
59
+ ```swift
60
+ // Thinking about threads
61
+ DispatchQueue.main.async {
62
+ // Update UI on main thread
63
+ }
64
+
65
+ DispatchQueue.global(qos: .background).async {
66
+ // Heavy work on background thread
67
+ }
68
+ ```
69
+
70
+ ### New way (Swift Concurrency)
71
+
72
+ ```swift
73
+ // Thinking about isolation domains
74
+ @MainActor
75
+ func updateUI() {
76
+ // Runs on main actor (usually main thread)
77
+ }
78
+
79
+ func heavyWork() async {
80
+ // Runs on any available thread in pool
81
+ }
82
+ ```
83
+
84
+ ### Think in isolation domains
85
+
86
+ **Don't ask**: "What thread should this run on?"
87
+
88
+ **Ask**: "What isolation domain should own this work?"
89
+
90
+ - `@MainActor` for UI updates
91
+ - Custom actors for specific state
92
+ - Nonisolated for general async work
93
+
94
+ ### Provide hints, not commands
95
+
96
+ ```swift
97
+ Task(priority: .userInitiated) {
98
+ await doWork()
99
+ }
100
+ ```
101
+
102
+ You're describing the nature of work, not assigning threads. Swift optimizes execution.
103
+
104
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 7.2: Getting rid of the "Threading Mindset"](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
105
+
106
+ ## Suspension Points
107
+
108
+ ### What is a suspension point?
109
+
110
+ Moment where task **may** pause to allow other work. Marked by `await`.
111
+
112
+ ```swift
113
+ let data = await fetchData() // Potential suspension
114
+ ```
115
+
116
+ **Critical**: `await` marks *possible* suspension, not guaranteed. If operation completes synchronously, no suspension occurs.
117
+
118
+ ### Why suspension points matter
119
+
120
+ 1. **Code may pause unexpectedly** - resumes later, possibly different thread
121
+ 2. **State can change** - mutable state may be modified during suspension
122
+ 3. **Actor reentrancy** - other tasks can access actor during suspension
123
+
124
+ ### Actor reentrancy example
125
+
126
+ ```swift
127
+ actor BankAccount {
128
+ private var balance: Int = 0
129
+
130
+ func deposit(amount: Int) async {
131
+ balance += amount
132
+ print("Balance: \(balance)")
133
+
134
+ await logTransaction(amount) // ⚠️ Suspension point
135
+
136
+ balance += 10 // Bonus
137
+ print("After bonus: \(balance)")
138
+ }
139
+
140
+ func logTransaction(_ amount: Int) async {
141
+ try? await Task.sleep(for: .seconds(1))
142
+ }
143
+ }
144
+
145
+ // Two concurrent deposits
146
+ async let _ = account.deposit(amount: 100)
147
+ async let _ = account.deposit(amount: 100)
148
+
149
+ // Unexpected: 100 → 200 → 210 → 220
150
+ // Expected: 100 → 110 → 210 → 220
151
+ ```
152
+
153
+ **Why**: During `logTransaction`, second deposit runs, modifying balance before first completes.
154
+
155
+ ### Avoiding reentrancy bugs
156
+
157
+ **Complete actor work before suspending**:
158
+
159
+ ```swift
160
+ func deposit(amount: Int) async {
161
+ balance += amount
162
+ balance += 10 // Bonus applied first
163
+ print("Final balance: \(balance)")
164
+
165
+ await logTransaction(amount) // Suspend after state changes
166
+ }
167
+ ```
168
+
169
+ **Rule**: Don't mutate actor state after suspension points.
170
+
171
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 7.3: Understanding Task suspension points](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
172
+
173
+ ## Thread Execution Patterns
174
+
175
+ ### Default: Background threads
176
+
177
+ Tasks run on cooperative thread pool (background threads):
178
+
179
+ ```swift
180
+ Task {
181
+ print(Thread.current) // Background thread
182
+ }
183
+ ```
184
+
185
+ ### Main thread execution
186
+
187
+ Use `@MainActor` for main thread:
188
+
189
+ ```swift
190
+ @MainActor
191
+ func updateUI() {
192
+ Task {
193
+ print(Thread.current) // Main thread
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### Inheritance example
199
+
200
+ ```swift
201
+ @MainActor
202
+ func updateUI() {
203
+ print("Main thread: \(Thread.current)")
204
+
205
+ await backgroundTask() // Switches to background
206
+
207
+ print("Back on main: \(Thread.current)") // Returns to main
208
+ }
209
+
210
+ func backgroundTask() async {
211
+ print("Background: \(Thread.current)")
212
+ }
213
+ ```
214
+
215
+ ## Swift 6.2 Changes
216
+
217
+ ### Nonisolated async functions (SE-461)
218
+
219
+ **Old behavior**: Nonisolated async functions always switch to background.
220
+
221
+ **New behavior**: Inherit caller's isolation by default.
222
+
223
+ ```swift
224
+ class NotSendable {
225
+ func performAsync() async {
226
+ print(Thread.current)
227
+ }
228
+ }
229
+
230
+ @MainActor
231
+ func caller() async {
232
+ let obj = NotSendable()
233
+ await obj.performAsync()
234
+ // Old: Background thread
235
+ // New: Main thread (inherits @MainActor)
236
+ }
237
+ ```
238
+
239
+ ### Enabling new behavior
240
+
241
+ In Xcode 16+:
242
+
243
+ ```swift
244
+ // Build setting or swift-settings
245
+ .enableUpcomingFeature("NonisolatedNonsendingByDefault")
246
+ ```
247
+
248
+ ### Opting out with @concurrent
249
+
250
+ Force function to switch away from caller's isolation:
251
+
252
+ ```swift
253
+ @concurrent
254
+ func performAsync() async {
255
+ print(Thread.current) // Always background
256
+ }
257
+ ```
258
+
259
+ ### nonisolated(nonsending)
260
+
261
+ Prevent sending non-Sendable values across isolation:
262
+
263
+ ```swift
264
+ nonisolated(nonsending) func storeTouch(...) async {
265
+ // Runs on caller's isolation, no value sending
266
+ }
267
+ ```
268
+
269
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 7.4: Dispatching to different threads using nonisolated(nonsending) and @concurrent (Updated for Swift 6.2)](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
270
+
271
+ **Use when**: Method doesn't need to switch isolation, avoiding Sendable requirements.
272
+
273
+ ## Default Isolation Domain (SE-466)
274
+
275
+ ### Configuring default isolation
276
+
277
+ **Build setting** (Xcode 16+):
278
+ - Default Actor Isolation: `MainActor` or `None`
279
+
280
+ **Swift Package**:
281
+
282
+ ```swift
283
+ .target(
284
+ name: "MyTarget",
285
+ swiftSettings: [
286
+ .defaultIsolation(MainActor.self)
287
+ ]
288
+ )
289
+ ```
290
+
291
+ ### Why change default?
292
+
293
+ Most app code runs on main thread. Setting `@MainActor` as default:
294
+ - Reduces false warnings
295
+ - Avoids "concurrency rabbit hole"
296
+ - Makes migration easier
297
+
298
+ ### Inference with @MainActor default
299
+
300
+ ```swift
301
+ // With @MainActor as default:
302
+
303
+ func f() {} // Inferred: @MainActor
304
+
305
+ class C {
306
+ init() {} // Inferred: @MainActor
307
+ static var value = 10 // Inferred: @MainActor
308
+ }
309
+
310
+ @MyActor
311
+ struct S {
312
+ func f() {} // Inferred: @MyActor (explicit override)
313
+ }
314
+
315
+ > **Course Deep Dive**: This topic is covered in detail in [Lesson 7.5: Controlling the default isolation domain (Updated for Swift 6.2)](https://www.swiftconcurrencycourse.com?utm_source=github&utm_medium=agent-skill&utm_campaign=lesson-reference)
316
+ ```
317
+
318
+ ### Per-module setting
319
+
320
+ Must opt in for each module/package. Not global across dependencies.
321
+
322
+ ### Backward compatibility
323
+
324
+ Opt-in only. Default remains `nonisolated` if not specified.
325
+
326
+ ## Debugging Thread Execution
327
+
328
+ ### Print current thread
329
+
330
+ **⚠️ Important**: `Thread.current` is unavailable in Swift 6 language mode from async contexts. The compiler error states: "Class property 'current' is unavailable from asynchronous contexts; Thread.current cannot be used from async contexts."
331
+
332
+ **Workaround** (Swift 6+ mode only):
333
+
334
+ ```swift
335
+ extension Thread {
336
+ public static var currentThread: Thread {
337
+ Thread.current
338
+ }
339
+ }
340
+
341
+ print("Thread: \(Thread.currentThread)")
342
+ ```
343
+
344
+ ### Debug navigator
345
+
346
+ 1. Set breakpoint in task
347
+ 2. Debug → Pause
348
+ 3. Check Debug Navigator for thread info
349
+
350
+ ### Verify main thread
351
+
352
+ ```swift
353
+ assert(Thread.isMainThread)
354
+ ```
355
+
356
+ ## Common Misconceptions
357
+
358
+ ### ❌ Each Task runs on new thread
359
+
360
+ **Wrong**. Tasks share limited thread pool, reuse threads.
361
+
362
+ ### ❌ await blocks the thread
363
+
364
+ **Wrong**. `await` suspends task without blocking thread. Other tasks can use the thread.
365
+
366
+ ### ❌ Task execution order is guaranteed
367
+
368
+ **Wrong**. Tasks execute based on system scheduling. Use `await` to enforce order.
369
+
370
+ ### ❌ Same task = same thread
371
+
372
+ **Wrong**. Task can resume on different thread after suspension.
373
+
374
+ ## Why Sendable Matters
375
+
376
+ Since tasks move between threads unpredictably:
377
+
378
+ ```swift
379
+ func example() async {
380
+ print("Thread 1: \(Thread.current)")
381
+
382
+ await someWork()
383
+
384
+ print("Thread 2: \(Thread.current)") // Different thread
385
+ }
386
+ ```
387
+
388
+ Values crossing suspension points may cross threads. **Sendable** ensures safety.
389
+
390
+ ## Best Practices
391
+
392
+ 1. **Stop thinking about threads** - think isolation domains
393
+ 2. **Trust the system** - Swift optimizes thread usage
394
+ 3. **Use @MainActor for UI** - clear, explicit main thread execution
395
+ 4. **Minimize suspension points in actors** - avoid reentrancy bugs
396
+ 5. **Complete state changes before suspending** - prevent inconsistent state
397
+ 6. **Use priorities as hints** - not guarantees
398
+ 7. **Make types Sendable** - safe across thread boundaries
399
+ 8. **Enable Swift 6.2 features** - easier migration, better defaults
400
+ 9. **Set default isolation for apps** - reduce false warnings
401
+ 10. **Don't force thread switching** - let Swift optimize
402
+
403
+ ## Migration Strategy
404
+
405
+ ### For new projects (Xcode 16+)
406
+
407
+ 1. Set default isolation to `@MainActor`
408
+ 2. Enable `NonisolatedNonsendingByDefault`
409
+ 3. Use `@concurrent` for explicit background work
410
+
411
+ ### For existing projects
412
+
413
+ 1. Gradually enable Swift 6 language mode
414
+ 2. Consider default isolation change
415
+ 3. Use `@concurrent` to maintain old behavior where needed
416
+ 4. Migrate module by module
417
+
418
+ ## Decision Tree
419
+
420
+ ```
421
+ Need to control execution?
422
+ ├─ UI updates? → @MainActor
423
+ ├─ Specific state isolation? → Custom actor
424
+ ├─ Background work? → Regular async (trust Swift)
425
+ └─ Need to force background? → @concurrent (Swift 6.2+)
426
+
427
+ Seeing Sendable warnings?
428
+ ├─ Can make type Sendable? → Add conformance
429
+ ├─ Same isolation OK? → nonisolated(nonsending)
430
+ └─ Need different isolation? → Make Sendable or refactor
431
+ ```
432
+
433
+ ## Performance Insights
434
+
435
+ ### Why fewer threads = better performance
436
+
437
+ - **Less context switching**: CPU spends more time on actual work
438
+ - **Better cache utilization**: Threads stay on same cores longer
439
+ - **No thread explosion**: Predictable resource usage
440
+ - **Forward progress**: Threads never block, always productive
441
+
442
+ ### Cooperative pool advantages
443
+
444
+ - Matches hardware (one thread per core)
445
+ - Prevents oversubscription
446
+ - Efficient task scheduling
447
+ - Automatic load balancing
448
+
449
+ ## Further Learning
450
+
451
+ For migration strategies, real-world examples, and advanced threading patterns, see [Swift Concurrency Course](https://www.swiftconcurrencycourse.com).
452
+
@@ -0,0 +1,18 @@
1
+ # swift-expert — Attribution Notice
2
+
3
+ Bundled into `swift-code-reviewer-skill` on 2026-04-21 from
4
+ `~/.maestro/skills/swift-expert`.
5
+
6
+ Primary author (best-effort attribution): **Eduardo Bocato ([@bocato](https://github.com/bocato))**
7
+ Also credited: [@AvdLee](https://github.com/AvdLee), [@Dimillian](https://github.com/Dimillian)
8
+
9
+ These three authors' public Swift/SwiftUI content — including SwiftLee, IceCubesApp,
10
+ and their various open-source contributions — informed the skills bundled here.
11
+
12
+ License: the upstream folder did not contain a LICENSE file at the time of vendoring.
13
+ Content is reproduced here in good faith for reference alongside this MIT-licensed
14
+ project. If you are an upstream author and want the attribution corrected, the license
15
+ clarified, or the content removed, please open an issue at:
16
+ https://github.com/Viniciuscarvalho/swift-code-reviewer-skill/issues
17
+
18
+ Changes from upstream: none (verbatim copy; `.DS_Store` files excluded).
@@ -0,0 +1,226 @@
1
+ ---
2
+ name: swift-expert
3
+ description: Swift specialist for iOS/macOS development with Swift 6.0+, SwiftUI, async/await concurrency, and protocol-oriented design. Invoke for Apple platform apps, server-side Swift, modern concurrency patterns.
4
+ ---
5
+
6
+ # Swift Expert
7
+
8
+ ## Reference Guide
9
+
10
+ | Topic | Reference | Load when |
11
+ | ----------- | ---------------------------------- | ------------------------------------------- |
12
+ | SwiftUI | `references/swiftui-patterns.md` | Building views, state management, modifiers |
13
+ | Concurrency | `references/async-concurrency.md` | async/await, actors, structured concurrency |
14
+ | Protocols | `references/protocol-oriented.md` | Protocol design, generics, type erasure |
15
+ | Memory | `references/memory-performance.md` | ARC, weak/unowned, performance optimization |
16
+ | Testing | `references/testing-patterns.md` | XCTest, async tests, mocking strategies |
17
+
18
+ ## Rules
19
+
20
+ **Always:**
21
+
22
+ - Use `async/await`; prefer structured concurrency (`TaskGroup`, child tasks) over `Task.detached`
23
+ - Annotate `@MainActor` on types that own UI-bound state; never mutate `@Observable` storage from a non-main-actor context
24
+ - Conform to `Sendable` when crossing actor boundaries; document the safety invariant on `@unchecked Sendable`
25
+ - Default to value types (`struct`/`enum`); use `class` only when reference semantics or identity are required
26
+ - Use `guard let` / `if let` / `try`; never `!` or `as!` without a justified comment explaining why it cannot fail
27
+
28
+ **Never:**
29
+
30
+ - Create retain cycles — capture `[weak self]` in escaping closures that outlive their owner
31
+ - Mix `DispatchQueue` and `async/await` in the same call path without an explicit isolation boundary
32
+ - Ignore actor isolation warnings; fix the root cause, do not reach for `nonisolated(unsafe)`
33
+ - Use `@preconcurrency` without a follow-up to remove it once the dependency is migrated
34
+
35
+ ## Code Examples
36
+
37
+ ### Swift 6 strict concurrency — actor + Sendable + @MainActor ViewModel
38
+
39
+ ```swift
40
+ // Value type: Sendable — safe to pass across isolation boundaries
41
+ struct Post: Sendable, Identifiable {
42
+ let id: UUID
43
+ let title: String
44
+ let body: String
45
+ }
46
+
47
+ // Service actor: owns mutable cache; callers must await
48
+ actor PostRepository {
49
+ private var cache: [UUID: Post] = [:]
50
+ private let client: NetworkClient // NetworkClient must itself be Sendable
51
+
52
+ func post(id: UUID) async throws -> Post {
53
+ if let hit = cache[id] { return hit }
54
+ let post = try await client.fetchPost(id: id)
55
+ cache[id] = post
56
+ return post
57
+ }
58
+ }
59
+
60
+ // ViewModel: @MainActor isolates all state to the main thread
61
+ @MainActor
62
+ @Observable
63
+ final class PostDetailViewModel {
64
+ var post: Post?
65
+ var isLoading = false
66
+ var error: Error?
67
+
68
+ private let repository: PostRepository
69
+
70
+ init(repository: PostRepository) {
71
+ self.repository = repository
72
+ }
73
+
74
+ func load(id: UUID) async {
75
+ isLoading = true
76
+ defer { isLoading = false }
77
+ do {
78
+ post = try await repository.post(id: id)
79
+ } catch {
80
+ self.error = error
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ Key points: `Post` is a `Sendable` value type, `PostRepository` is an actor so its `cache` is mutation-safe, and the ViewModel is `@MainActor` so all `@Observable` storage updates are on the main thread — zero data races under Swift 6 strict concurrency.
87
+
88
+ ---
89
+
90
+ ### SwiftUI @Observable + @Environment dependency injection
91
+
92
+ ```swift
93
+ // Shared router injected via Environment — avoids prop-drilling through view tree
94
+ @Observable
95
+ final class AppRouter {
96
+ var path: [Route] = []
97
+ func push(_ route: Route) { path.append(route) }
98
+ func pop() { guard !path.isEmpty else { return }; path.removeLast() }
99
+ }
100
+
101
+ @main
102
+ struct MyApp: App {
103
+ @State private var router = AppRouter()
104
+
105
+ var body: some Scene {
106
+ WindowGroup {
107
+ RootView()
108
+ .environment(router)
109
+ }
110
+ }
111
+ }
112
+
113
+ // Any descendant reads the router without an explicit init parameter
114
+ struct FeedView: View {
115
+ @Environment(AppRouter.self) private var router
116
+ let posts: [Post]
117
+
118
+ var body: some View {
119
+ List(posts) { post in
120
+ PostRow(post: post)
121
+ .onTapGesture { router.push(.postDetail(post.id)) }
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ Use `@Environment` for cross-cutting shared objects (router, theme, auth state). Use `@State` in the root scene — not as a global — so the object lifetime is tied to the window.
128
+
129
+ ---
130
+
131
+ ### Protocol-oriented design with primary associated types
132
+
133
+ ```swift
134
+ // Primary associated type enables `some` / `any` at call sites
135
+ protocol DataStore<Value>: Sendable {
136
+ associatedtype Value: Sendable & Identifiable
137
+ func fetch(id: Value.ID) async throws -> Value
138
+ func save(_ value: Value) async throws
139
+ }
140
+
141
+ // Concrete implementation is an actor for thread-safe storage
142
+ actor UserStore: DataStore {
143
+ typealias Value = User
144
+ private var db: [User.ID: User] = [:]
145
+
146
+ func fetch(id: User.ID) async throws -> User {
147
+ guard let user = db[id] else { throw StoreError.notFound(id) }
148
+ return user
149
+ }
150
+ func save(_ value: User) async throws { db[value.id] = value }
151
+ }
152
+
153
+ // Generic function: no `any` boxing, full type-checking, parallel prefetch
154
+ func prefetch<Store: DataStore>(
155
+ ids: [Store.Value.ID],
156
+ from store: Store
157
+ ) async throws -> [Store.Value] {
158
+ try await withThrowingTaskGroup(of: Store.Value.self) { group in
159
+ for id in ids { group.addTask { try await store.fetch(id: id) } }
160
+ return try await group.reduce(into: []) { $0.append($1) }
161
+ }
162
+ }
163
+
164
+ // Opaque return: caller gets full type-checking, implementation stays hidden
165
+ func makeUserStore() -> some DataStore<User> { UserStore() }
166
+ ```
167
+
168
+ Prefer `some DataStore<User>` over `any DataStore<User>` when the concrete type is fixed at the call site — no heap allocation for the existential box.
169
+
170
+ ## Project Structure
171
+
172
+ Feature-slice layout scales better than layer-slice beyond three features:
173
+
174
+ ```
175
+ Sources/
176
+ App/ ← entry point, root scene, environment wiring only
177
+ Features/
178
+ Feed/ ← FeedView, FeedViewModel, FeedRepository (co-located)
179
+ Profile/
180
+ Settings/
181
+ Core/
182
+ Models/ ← Sendable value types, no UIKit/SwiftUI imports
183
+ Network/ ← NetworkClient protocol + actor implementation
184
+ Storage/ ← persistence actors
185
+ DesignSystem/ ← colors, fonts, reusable views — zero business logic
186
+ Tests/
187
+ FeedTests/
188
+ CoreTests/
189
+ ```
190
+
191
+ **Extract a Swift Package target when:**
192
+
193
+ - A feature or layer needs to be mocked in tests without importing the concrete dependency
194
+ - A module is shared by an app extension (widget, share extension, Watch app)
195
+ - A build-time regression is traced to a specific module boundary
196
+
197
+ Minimum viable `Package.swift` for a modular app:
198
+
199
+ ```swift
200
+ // swift-tools-version: 6.0
201
+ let package = Package(
202
+ name: "MyApp",
203
+ platforms: [.iOS(.v17), .macOS(.v14)],
204
+ targets: [
205
+ .target(
206
+ name: "Core",
207
+ dependencies: [],
208
+ swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
209
+ ),
210
+ .target(
211
+ name: "DesignSystem",
212
+ dependencies: []
213
+ ),
214
+ .target(
215
+ name: "Features",
216
+ dependencies: ["Core", "DesignSystem"]
217
+ ),
218
+ .testTarget(
219
+ name: "FeaturesTests",
220
+ dependencies: ["Features"]
221
+ ),
222
+ ]
223
+ )
224
+ ```
225
+
226
+ `Core` has no upward dependencies. `Features` imports `Core` and `DesignSystem`. Tests import only the target under test.