@pilllesss/yorn 1.0.182 → 1.0.183

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.

Potentially problematic release.


This version of @pilllesss/yorn might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/providers/data/.manifest.json +1 -1
  3. package/dist/skills/code-review/LICENSE +21 -0
  4. package/dist/skills/code-review/SKILL.md +233 -0
  5. package/dist/skills/code-review/assets/pr-review-template.md +137 -0
  6. package/dist/skills/code-review/assets/review-checklist.md +123 -0
  7. package/dist/skills/code-review/reference/angular.md +768 -0
  8. package/dist/skills/code-review/reference/architecture-review-guide.md +472 -0
  9. package/dist/skills/code-review/reference/c.md +890 -0
  10. package/dist/skills/code-review/reference/code-quality-universal.md +488 -0
  11. package/dist/skills/code-review/reference/code-review-best-practices.md +136 -0
  12. package/dist/skills/code-review/reference/common-bugs-checklist.md +302 -0
  13. package/dist/skills/code-review/reference/cpp.md +893 -0
  14. package/dist/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  15. package/dist/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
  16. package/dist/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
  17. package/dist/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
  18. package/dist/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
  19. package/dist/skills/code-review/reference/csharp.md +525 -0
  20. package/dist/skills/code-review/reference/css-less-sass.md +661 -0
  21. package/dist/skills/code-review/reference/dart.md +670 -0
  22. package/dist/skills/code-review/reference/django.md +985 -0
  23. package/dist/skills/code-review/reference/fastapi.md +580 -0
  24. package/dist/skills/code-review/reference/go.md +993 -0
  25. package/dist/skills/code-review/reference/java.md +409 -0
  26. package/dist/skills/code-review/reference/java8.md +586 -0
  27. package/dist/skills/code-review/reference/kotlin.md +1018 -0
  28. package/dist/skills/code-review/reference/nestjs.md +593 -0
  29. package/dist/skills/code-review/reference/performance-review-guide.md +816 -0
  30. package/dist/skills/code-review/reference/php.md +684 -0
  31. package/dist/skills/code-review/reference/python.md +1073 -0
  32. package/dist/skills/code-review/reference/qt.md +757 -0
  33. package/dist/skills/code-review/reference/react.md +871 -0
  34. package/dist/skills/code-review/reference/ruby.md +964 -0
  35. package/dist/skills/code-review/reference/rust.md +846 -0
  36. package/dist/skills/code-review/reference/security-review-guide.md +494 -0
  37. package/dist/skills/code-review/reference/svelte.md +1064 -0
  38. package/dist/skills/code-review/reference/swift.md +936 -0
  39. package/dist/skills/code-review/reference/typescript.md +1016 -0
  40. package/dist/skills/code-review/reference/vue.md +924 -0
  41. package/dist/skills/code-review/reference/zig.md +440 -0
  42. package/dist/skills/code-review/scripts/pr-analyzer.py +435 -0
  43. package/dist/skills/code-review/scripts/test_pr_analyzer.py +380 -0
  44. package/dist/yorn.cjs +628 -628
  45. package/package.json +2 -2
@@ -0,0 +1,936 @@
1
+ # Swift Code Review Guide
2
+
3
+ A code review checklist for modern Swift (5.9+/6), covering SwiftUI, Swift Concurrency, and the Swift API Design Guidelines.
4
+
5
+ ## Quick Review Checklist
6
+
7
+ ### Must-Check Items
8
+ - [ ] Are force-unwraps (`!`) and `try!` avoided in favor of safe unwrapping
9
+ - [ ] Do closures that capture `self` use `[weak self]` to avoid retain cycles
10
+ - [ ] Is the value vs reference type choice intentional (struct vs class)
11
+ - [ ] Are errors propagated with `throws`/`Result` instead of being swallowed
12
+ - [ ] Are concurrency boundaries data-race-safe (`Sendable`, `@MainActor`, actors)
13
+
14
+ ### Common Issues
15
+ - [ ] Fire-and-forget `Task {}` that leaks or is never cancelled
16
+ - [ ] Wrong SwiftUI property wrapper (`@ObservedObject` where `@StateObject` is needed)
17
+ - [ ] O(n^2) lookups in loops that could use a `Set` or `Dictionary`
18
+ - [ ] Implicitly unwrapped optionals (`var x: T!`) outside of IBOutlets
19
+ - [ ] Over-broad access control (`public`/`open` where `internal` suffices)
20
+ - [ ] Naming that ignores the Swift API Design Guidelines
21
+
22
+ ---
23
+
24
+ ## 1. Optionals and Unwrapping
25
+
26
+ ### 1.1 Avoid Force-Unwrapping
27
+
28
+ ```swift
29
+ // ❌ Wrong: crashes at runtime if nil
30
+ let name = user.name!
31
+ let url = URL(string: urlString)!
32
+
33
+ // ✅ Correct: bind with guard let / if let
34
+ guard let name = user.name else {
35
+ return
36
+ }
37
+
38
+ if let url = URL(string: urlString) {
39
+ load(url)
40
+ }
41
+ ```
42
+
43
+ ### 1.2 Use Nil-Coalescing for Defaults
44
+
45
+ ```swift
46
+ // ❌ Wrong: verbose and crash-prone
47
+ let count: Int
48
+ if let c = dictionary["count"] {
49
+ count = c
50
+ } else {
51
+ count = 0
52
+ }
53
+
54
+ // ✅ Correct: nil-coalescing
55
+ let count = dictionary["count"] ?? 0
56
+ ```
57
+
58
+ ### 1.3 Prefer guard let for Early Exit
59
+
60
+ ```swift
61
+ // ❌ Wrong: deep nesting (pyramid of doom)
62
+ func process(_ input: String?) {
63
+ if let input = input {
64
+ if let value = Int(input) {
65
+ if value > 0 {
66
+ handle(value)
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ // ✅ Correct: guard keeps the happy path unindented
73
+ func process(_ input: String?) {
74
+ guard let input,
75
+ let value = Int(input),
76
+ value > 0 else {
77
+ return
78
+ }
79
+ handle(value)
80
+ }
81
+ ```
82
+
83
+ ### 1.4 Avoid Implicitly Unwrapped Optionals
84
+
85
+ ```swift
86
+ // ❌ Wrong: T! is a hidden force-unwrap on every access
87
+ class ViewModel {
88
+ var service: NetworkService!
89
+ }
90
+
91
+ // ✅ Correct: inject a non-optional dependency
92
+ class ViewModel {
93
+ private let service: NetworkService
94
+
95
+ init(service: NetworkService) {
96
+ self.service = service
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### 1.5 Use Optional Chaining and map/flatMap
102
+
103
+ ```swift
104
+ // ❌ Wrong: manual unwrapping just to transform
105
+ var initial: String?
106
+ if let name = user.name {
107
+ initial = String(name.prefix(1))
108
+ }
109
+
110
+ // ✅ Correct: optional chaining + map
111
+ let initial = user.name.map { String($0.prefix(1)) }
112
+
113
+ // ✅ Correct: flatMap to avoid double optionals
114
+ let port: Int? = components.port.flatMap { Int(exactly: $0) }
115
+ ```
116
+
117
+ ---
118
+
119
+ ## 2. Memory Management and Retain Cycles
120
+
121
+ ### 2.1 Use [weak self] in Escaping Closures
122
+
123
+ ```swift
124
+ // ❌ Wrong: closure strongly captures self, creating a retain cycle
125
+ class ImageLoader {
126
+ var onComplete: (() -> Void)?
127
+
128
+ func load() {
129
+ service.fetch { data in
130
+ self.cache = data // self is retained by the closure
131
+ self.onComplete?()
132
+ }
133
+ }
134
+ }
135
+
136
+ // ✅ Correct: capture self weakly and guard
137
+ class ImageLoader {
138
+ var onComplete: (() -> Void)?
139
+
140
+ func load() {
141
+ service.fetch { [weak self] data in
142
+ guard let self else { return }
143
+ self.cache = data
144
+ self.onComplete?()
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### 2.2 weak vs unowned
151
+
152
+ ```swift
153
+ // ✅ Use weak when the reference can legitimately become nil
154
+ class Controller {
155
+ weak var delegate: ControllerDelegate?
156
+ }
157
+
158
+ // ✅ Use unowned only when the captured object is guaranteed to
159
+ // outlive the closure (e.g. self owns the closure tightly).
160
+ // unowned crashes if accessed after deallocation.
161
+ class Owner {
162
+ lazy var describe: () -> String = { [unowned self] in
163
+ self.name
164
+ }
165
+ let name = "owner"
166
+ }
167
+
168
+ // ❌ Wrong: unowned on something that can outlive self -> crash
169
+ networkClient.onResponse = { [unowned self] in self.update() }
170
+ // Prefer [weak self] here, since onResponse may fire after self is gone.
171
+ ```
172
+
173
+ ### 2.3 Break Delegate Retain Cycles
174
+
175
+ ```swift
176
+ // ❌ Wrong: strong delegate keeps both objects alive forever
177
+ protocol DataSourceDelegate: AnyObject {}
178
+
179
+ class DataSource {
180
+ var delegate: DataSourceDelegate? // strong by default
181
+ }
182
+
183
+ // ✅ Correct: delegates should be weak (and protocol AnyObject-bound)
184
+ class DataSource {
185
+ weak var delegate: DataSourceDelegate?
186
+ }
187
+ ```
188
+
189
+ ### 2.4 Closures Stored as Properties
190
+
191
+ ```swift
192
+ // ❌ Wrong: stored closure captures self strongly -> permanent cycle
193
+ class Timer {
194
+ var tick: (() -> Void)!
195
+ func configure() {
196
+ tick = { self.count += 1 }
197
+ }
198
+ var count = 0
199
+ }
200
+
201
+ // ✅ Correct: weak capture for stored closures referencing self
202
+ class Timer {
203
+ var tick: (() -> Void)?
204
+ func configure() {
205
+ tick = { [weak self] in self?.count += 1 }
206
+ }
207
+ var count = 0
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ## 3. Value vs Reference Types
214
+
215
+ ### 3.1 Prefer Structs by Default
216
+
217
+ ```swift
218
+ // ✅ Use a struct for data/models with value semantics
219
+ struct Coordinate {
220
+ var latitude: Double
221
+ var longitude: Double
222
+ }
223
+
224
+ // Copies are independent; no shared mutable state, thread-friendly.
225
+ var a = Coordinate(latitude: 1, longitude: 2)
226
+ var b = a
227
+ b.latitude = 99 // a is unchanged
228
+ ```
229
+
230
+ ### 3.2 Use a Class for Identity or Shared State
231
+
232
+ ```swift
233
+ // ✅ Use a class when instances have identity or must be shared/mutated
234
+ // by reference, or when you need inheritance / Objective-C interop.
235
+ final class DatabaseConnection {
236
+ private(set) var isOpen = false
237
+ func open() { isOpen = true }
238
+ }
239
+
240
+ // Two references point to the same connection.
241
+ let conn1 = DatabaseConnection()
242
+ let conn2 = conn1
243
+ conn1.open()
244
+ // conn2.isOpen == true
245
+ ```
246
+
247
+ ### 3.3 Mark Classes final When Not Subclassed
248
+
249
+ ```swift
250
+ // ❌ Wrong: open to subclassing unintentionally (slower dispatch, fragile API)
251
+ class UserViewModel {}
252
+
253
+ // ✅ Correct: final enables static dispatch and signals intent
254
+ final class UserViewModel {}
255
+ ```
256
+
257
+ ### 3.4 Beware Reference Types Inside Structs
258
+
259
+ ```swift
260
+ // ❌ Surprising: struct copy still shares the inner class instance
261
+ final class Box { var value = 0 }
262
+ struct Container { var box = Box() }
263
+
264
+ var x = Container()
265
+ var y = x
266
+ y.box.value = 42 // x.box.value is also 42 (shared reference!)
267
+
268
+ // ✅ Correct: use value semantics throughout, or copy on write deliberately
269
+ struct Container {
270
+ var value = 0 // plain value type, copies are independent
271
+ }
272
+ ```
273
+
274
+ ---
275
+
276
+ ## 4. Error Handling
277
+
278
+ > 📖 For cross-language error handling principles, see [Error Handling Guide](cross-cutting/error-handling-principles.md)
279
+
280
+ ### 4.1 Avoid try! and try?
281
+
282
+ ```swift
283
+ // ❌ Wrong: try! crashes on any thrown error
284
+ let data = try! Data(contentsOf: url)
285
+
286
+ // ❌ Often wrong: try? silently discards the error and the cause
287
+ let data = try? Data(contentsOf: url) // data is nil, you lose "why"
288
+
289
+ // ✅ Correct: propagate or handle with do-catch
290
+ do {
291
+ let data = try Data(contentsOf: url)
292
+ process(data)
293
+ } catch {
294
+ log.error("failed to read \(url): \(error)")
295
+ }
296
+ ```
297
+
298
+ ### 4.2 Define Meaningful Error Types
299
+
300
+ ```swift
301
+ // ✅ Recommended: an Error enum communicates failure modes precisely
302
+ enum NetworkError: Error {
303
+ case invalidURL
304
+ case unauthorized
305
+ case server(statusCode: Int)
306
+ case decoding(underlying: Error)
307
+ }
308
+
309
+ func fetch(_ path: String) throws -> Data {
310
+ guard let url = URL(string: path) else {
311
+ throw NetworkError.invalidURL
312
+ }
313
+ // ...
314
+ }
315
+ ```
316
+
317
+ ### 4.3 Use Result for Stored or Deferred Outcomes
318
+
319
+ ```swift
320
+ // ✅ Result is useful at callback boundaries or when storing an outcome
321
+ func load(completion: @escaping (Result<User, NetworkError>) -> Void) {
322
+ // completion(.success(user)) or completion(.failure(.unauthorized))
323
+ }
324
+
325
+ // ✅ Convert between Result and throws as needed
326
+ let user = try result.get()
327
+ ```
328
+
329
+ ### 4.4 Typed Throws (Swift 6)
330
+
331
+ ```swift
332
+ // ✅ Typed throws constrains the error type when it is fully known.
333
+ // Use it for closed, exhaustive error domains; prefer untyped
334
+ // `throws` for library APIs that may grow new error cases.
335
+ func parse(_ raw: String) throws(ParsingError) -> Token {
336
+ guard let token = Token(raw) else {
337
+ throw ParsingError.malformed
338
+ }
339
+ return token
340
+ }
341
+
342
+ do {
343
+ let token = try parse(input)
344
+ } catch {
345
+ // `error` is statically known to be ParsingError
346
+ handle(error)
347
+ }
348
+ ```
349
+
350
+ ### 4.5 Don't Catch and Rethrow Without Value
351
+
352
+ ```swift
353
+ // ❌ Wrong: catch that adds nothing but obscures the trace
354
+ do {
355
+ try work()
356
+ } catch {
357
+ throw error // pointless
358
+ }
359
+
360
+ // ✅ Correct: only catch to add context or recover
361
+ do {
362
+ try work()
363
+ } catch {
364
+ throw AppError.workFailed(underlying: error)
365
+ }
366
+ ```
367
+
368
+ ---
369
+
370
+ ## 5. Swift Concurrency
371
+
372
+ > 📖 For cross-language concurrency patterns, see [Async & Concurrency Guide](cross-cutting/async-concurrency-patterns.md)
373
+
374
+ ### 5.1 Prefer async/await Over Nested Callbacks
375
+
376
+ ```swift
377
+ // ❌ Wrong: callback pyramid, error handling scattered
378
+ func loadProfile(completion: @escaping (Result<Profile, Error>) -> Void) {
379
+ fetchUser { userResult in
380
+ switch userResult {
381
+ case .success(let user):
382
+ fetchAvatar(user) { avatarResult in /* ... */ }
383
+ case .failure(let error):
384
+ completion(.failure(error))
385
+ }
386
+ }
387
+ }
388
+
389
+ // ✅ Correct: linear async/await
390
+ func loadProfile() async throws -> Profile {
391
+ let user = try await fetchUser()
392
+ let avatar = try await fetchAvatar(user)
393
+ return Profile(user: user, avatar: avatar)
394
+ }
395
+ ```
396
+
397
+ ### 5.2 Use @MainActor for UI State
398
+
399
+ ```swift
400
+ // ❌ Wrong: mutating UI state from a background context (data race / crash)
401
+ func refresh() async {
402
+ let items = try? await api.load()
403
+ self.items = items ?? [] // may run off the main thread
404
+ }
405
+
406
+ // ✅ Correct: isolate UI-facing types to the main actor
407
+ @MainActor
408
+ final class FeedViewModel: ObservableObject {
409
+ @Published var items: [Item] = []
410
+
411
+ func refresh() async {
412
+ let loaded = (try? await api.load()) ?? []
413
+ items = loaded // guaranteed on the main actor
414
+ }
415
+ }
416
+ ```
417
+
418
+ ### 5.3 Protect Mutable State with Actors
419
+
420
+ ```swift
421
+ // ❌ Wrong: shared mutable state without synchronization (data race)
422
+ final class Counter {
423
+ var value = 0
424
+ func increment() { value += 1 }
425
+ }
426
+
427
+ // ✅ Correct: an actor serializes access to its mutable state
428
+ actor Counter {
429
+ private(set) var value = 0
430
+ func increment() { value += 1 }
431
+ }
432
+
433
+ let counter = Counter()
434
+ await counter.increment() // access is awaited and serialized
435
+ ```
436
+
437
+ ### 5.4 Conform Shared Types to Sendable
438
+
439
+ ```swift
440
+ // ❌ Wrong: passing a non-Sendable class across actors (Swift 6 error)
441
+ final class Config { // mutable, not Sendable
442
+ var retries = 3
443
+ }
444
+
445
+ // ✅ Correct: make shared types Sendable (immutable value type is ideal)
446
+ struct Config: Sendable {
447
+ let retries: Int
448
+ }
449
+
450
+ // ✅ For reference types, use final + immutable stored properties,
451
+ // or @unchecked Sendable only with manual synchronization.
452
+ final class Cache: @unchecked Sendable {
453
+ private let lock = NSLock()
454
+ private var storage: [String: Data] = [:]
455
+ // all access guarded by lock
456
+ }
457
+ ```
458
+
459
+ ### 5.5 Handle Task Cancellation
460
+
461
+ ```swift
462
+ // ❌ Wrong: ignores cancellation, keeps working after the view is gone
463
+ func search(_ query: String) async -> [Result] {
464
+ var results: [Result] = []
465
+ for page in 0..<100 {
466
+ results += await fetchPage(query, page) // never stops
467
+ }
468
+ return results
469
+ }
470
+
471
+ // ✅ Correct: check for cancellation cooperatively
472
+ func search(_ query: String) async throws -> [Result] {
473
+ var results: [Result] = []
474
+ for page in 0..<100 {
475
+ try Task.checkCancellation()
476
+ results += try await fetchPage(query, page)
477
+ }
478
+ return results
479
+ }
480
+ ```
481
+
482
+ ### 5.6 Don't Leak Fire-and-Forget Tasks
483
+
484
+ ```swift
485
+ // ❌ Wrong: unstructured Task with no handle, never cancelled
486
+ final class ViewModel {
487
+ func onAppear() {
488
+ Task {
489
+ await self.stream() // runs forever even after dismissal
490
+ }
491
+ }
492
+ }
493
+
494
+ // ✅ Correct: retain the handle and cancel it (or use .task in SwiftUI)
495
+ final class ViewModel {
496
+ private var streamTask: Task<Void, Never>?
497
+
498
+ func onAppear() {
499
+ streamTask = Task { [weak self] in
500
+ await self?.stream()
501
+ }
502
+ }
503
+
504
+ func onDisappear() {
505
+ streamTask?.cancel()
506
+ }
507
+ }
508
+ ```
509
+
510
+ ### 5.7 Use Structured Concurrency for Parallelism
511
+
512
+ ```swift
513
+ // ❌ Wrong: sequential awaits where work could run concurrently
514
+ let a = await loadA()
515
+ let b = await loadB() // waits for A to finish first
516
+
517
+ // ✅ Correct: async let runs them concurrently
518
+ async let a = loadA()
519
+ async let b = loadB()
520
+ let (resultA, resultB) = await (a, b)
521
+
522
+ // ✅ For a dynamic number of children, use a task group
523
+ try await withThrowingTaskGroup(of: Item.self) { group in
524
+ for id in ids {
525
+ group.addTask { try await fetch(id) }
526
+ }
527
+ for try await item in group {
528
+ store(item)
529
+ }
530
+ }
531
+ ```
532
+
533
+ ---
534
+
535
+ ## 6. SwiftUI
536
+
537
+ ### 6.1 Choose the Right State Wrapper
538
+
539
+ ```swift
540
+ // ✅ @State: simple value-type state owned by this view
541
+ struct Toggle: View {
542
+ @State private var isOn = false
543
+ var body: some View { /* ... */ }
544
+ }
545
+
546
+ // ✅ @StateObject: the view CREATES and OWNS a reference-type model
547
+ struct ProfileScreen: View {
548
+ @StateObject private var model = ProfileViewModel()
549
+ var body: some View { /* ... */ }
550
+ }
551
+
552
+ // ✅ @ObservedObject: the model is OWNED elsewhere and passed in
553
+ struct ProfileHeader: View {
554
+ @ObservedObject var model: ProfileViewModel
555
+ var body: some View { /* ... */ }
556
+ }
557
+
558
+ // ✅ @Binding: a two-way reference to state owned by a parent
559
+ struct SearchField: View {
560
+ @Binding var text: String
561
+ var body: some View { /* ... */ }
562
+ }
563
+ ```
564
+
565
+ ### 6.2 @StateObject vs @ObservedObject
566
+
567
+ ```swift
568
+ // ❌ Wrong: @ObservedObject for an object the view itself creates.
569
+ // SwiftUI may recreate the view, re-instantiating the model and
570
+ // losing its state on every re-render.
571
+ struct CounterView: View {
572
+ @ObservedObject var model = CounterModel() // recreated unexpectedly
573
+ }
574
+
575
+ // ✅ Correct: @StateObject ties the model's lifetime to the view
576
+ struct CounterView: View {
577
+ @StateObject private var model = CounterModel()
578
+ }
579
+ ```
580
+
581
+ ### 6.3 Preserve View Identity
582
+
583
+ ```swift
584
+ // ❌ Wrong: index-based id reuses identity when the array reorders,
585
+ // causing wrong animations and stale state.
586
+ ForEach(0..<items.count, id: \.self) { i in
587
+ ItemRow(item: items[i])
588
+ }
589
+
590
+ // ✅ Correct: use a stable, unique identifier
591
+ ForEach(items) { item in // Item: Identifiable
592
+ ItemRow(item: item)
593
+ }
594
+
595
+ // ✅ Use .id(...) to deliberately reset a view's state
596
+ ProfileView(user: user)
597
+ .id(user.id) // new identity per user -> fresh state
598
+ ```
599
+
600
+ ### 6.4 Avoid Over-Rendering
601
+
602
+ ```swift
603
+ // ❌ Wrong: a single huge body re-renders everything on any change
604
+ struct Dashboard: View {
605
+ @ObservedObject var model: DashboardModel
606
+ var body: some View {
607
+ VStack {
608
+ // header + heavy chart + list all recompute together
609
+ }
610
+ }
611
+ }
612
+
613
+ // ✅ Correct: extract subviews so only the affected part re-renders.
614
+ // Each child observes only the state it needs.
615
+ struct Dashboard: View {
616
+ var body: some View {
617
+ VStack {
618
+ HeaderView()
619
+ ChartView()
620
+ ItemList()
621
+ }
622
+ }
623
+ }
624
+ ```
625
+
626
+ ### 6.5 Do Async Work with .task
627
+
628
+ ```swift
629
+ // ❌ Wrong: kicking off work in onAppear without cancellation
630
+ .onAppear {
631
+ Task { await model.load() } // not cancelled when view disappears
632
+ }
633
+
634
+ // ✅ Correct: .task is tied to the view's lifetime and auto-cancels
635
+ .task {
636
+ await model.load()
637
+ }
638
+
639
+ // ✅ Re-run when an input changes
640
+ .task(id: query) {
641
+ await model.search(query)
642
+ }
643
+ ```
644
+
645
+ ---
646
+
647
+ ## 7. Protocols and Generics
648
+
649
+ ### 7.1 Protocol-Oriented Design
650
+
651
+ ```swift
652
+ // ✅ Compose behavior with protocols and default implementations
653
+ protocol Identifiable2 {
654
+ var id: String { get }
655
+ }
656
+
657
+ protocol Describable {
658
+ var description: String { get }
659
+ }
660
+
661
+ extension Describable {
662
+ var description: String { "no description" } // default
663
+ }
664
+ ```
665
+
666
+ ### 7.2 Prefer some Over any
667
+
668
+ ```swift
669
+ // ❌ Slower: `any` is an existential box with dynamic dispatch
670
+ func makeShape() -> any Shape { Circle() }
671
+
672
+ // ✅ Faster: `some` is an opaque type resolved at compile time,
673
+ // preserving the concrete type and enabling static dispatch.
674
+ func makeShape() -> some Shape { Circle() }
675
+
676
+ // Use `any` only when you genuinely need heterogeneous values:
677
+ let shapes: [any Shape] = [Circle(), Square()]
678
+ ```
679
+
680
+ ### 7.3 Generic Constraints Over Existentials
681
+
682
+ ```swift
683
+ // ❌ Wrong: existential parameter loses the concrete type and is slower
684
+ func logTotal(_ items: [any Numeric]) {
685
+ // awkward: the concrete numeric type is erased, so arithmetic needs casts
686
+ }
687
+
688
+ // ✅ Correct: a generic constraint keeps full type information
689
+ func total<T: Numeric>(_ items: [T]) -> T {
690
+ items.reduce(.zero, +)
691
+ }
692
+ ```
693
+
694
+ ### 7.4 Associated Types with Primary Associated Types
695
+
696
+ ```swift
697
+ // ✅ Primary associated types (Swift 5.7+) allow lightweight constraints
698
+ protocol Container<Item> {
699
+ associatedtype Item
700
+ var count: Int { get }
701
+ subscript(_ index: Int) -> Item { get }
702
+ }
703
+
704
+ // Constrain the element type without a where-clause:
705
+ func first(in container: some Container<Int>) -> Int {
706
+ container[0]
707
+ }
708
+ ```
709
+
710
+ ---
711
+
712
+ ## 8. Access Control and API Design
713
+
714
+ ### 8.1 Use the Narrowest Access Level
715
+
716
+ ```swift
717
+ // ❌ Wrong: everything public exposes internal details as API surface
718
+ public class Service {
719
+ public var cache: [String: Data] = [:]
720
+ public func reset() {}
721
+ }
722
+
723
+ // ✅ Correct: expose only the intended API; hide the rest
724
+ public final class Service {
725
+ private var cache: [String: Data] = [:]
726
+ public func reset() { cache.removeAll() }
727
+ }
728
+ ```
729
+
730
+ ### 8.2 private vs fileprivate vs internal vs public/open
731
+
732
+ ```swift
733
+ // private: visible only within the enclosing declaration (and its extensions in the same file)
734
+ // fileprivate: visible within the same source file
735
+ // internal: visible within the module (the default)
736
+ // public: visible outside the module, but not subclassable/overridable
737
+ // open: visible outside the module AND subclassable/overridable
738
+
739
+ // ✅ Use private(set) to expose read-only state
740
+ public final class Account {
741
+ public private(set) var balance: Decimal = 0
742
+ }
743
+ ```
744
+
745
+ ### 8.3 Follow the Swift API Design Guidelines
746
+
747
+ ```swift
748
+ // ❌ Wrong: redundant words, unclear argument roles
749
+ func insertObject(_ object: Element, atIndex index: Int)
750
+ list.removeElement(at: 0)
751
+
752
+ // ✅ Correct: read at the call site like a phrase; omit needless words
753
+ func insert(_ element: Element, at index: Int)
754
+ list.insert(item, at: 0) // reads as "insert item at 0"
755
+ list.remove(at: 0)
756
+
757
+ // ✅ Boolean properties read as assertions
758
+ var isEmpty: Bool
759
+ var hasChanges: Bool
760
+ ```
761
+
762
+ ### 8.4 Name Methods by Side Effects
763
+
764
+ ```swift
765
+ // ✅ Mutating verb vs non-mutating noun pairs (the "ed/ing" rule)
766
+ var sorted = array.sorted() // returns a new value (non-mutating)
767
+ array.sort() // mutates in place (imperative verb)
768
+
769
+ let reversed = text.reversed()
770
+ text.reverse()
771
+ ```
772
+
773
+ ---
774
+
775
+ ## 9. Collections and Functional Style
776
+
777
+ ### 9.1 Prefer map/filter/compactMap
778
+
779
+ ```swift
780
+ // ❌ Verbose: manual loop with mutable accumulator
781
+ var names: [String] = []
782
+ for user in users {
783
+ if user.isActive {
784
+ names.append(user.name)
785
+ }
786
+ }
787
+
788
+ // ✅ Correct: declarative transform
789
+ let names = users.filter(\.isActive).map(\.name)
790
+ ```
791
+
792
+ ### 9.2 compactMap to Drop nils
793
+
794
+ ```swift
795
+ // ❌ Wrong: map leaves an [Int?] you then have to unwrap
796
+ let numbers = strings.map { Int($0) } // [Int?]
797
+
798
+ // ✅ Correct: compactMap removes nils and unwraps
799
+ let numbers = strings.compactMap { Int($0) } // [Int]
800
+ ```
801
+
802
+ ### 9.3 Avoid O(n^2) Membership Checks
803
+
804
+ ```swift
805
+ // ❌ Wrong: contains on an Array is O(n); the loop is O(n*m)
806
+ let result = candidates.filter { blocked.contains($0) } // blocked: [ID]
807
+
808
+ // ✅ Correct: a Set makes membership O(1)
809
+ let blockedSet = Set(blocked)
810
+ let result = candidates.filter { blockedSet.contains($0) }
811
+ ```
812
+
813
+ ### 9.4 reduce and Dictionary Grouping
814
+
815
+ ```swift
816
+ // ✅ Group with Dictionary(grouping:)
817
+ let byFirstLetter = Dictionary(grouping: words) { $0.first }
818
+
819
+ // ❌ Wrong: reduce(into:) is preferred over reduce that copies each step
820
+ let total = numbers.reduce(0) { $0 + $1 } // fine for scalars
821
+
822
+ // ✅ Use reduce(into:) when accumulating into a collection (avoids copies)
823
+ let counts = words.reduce(into: [:]) { acc, word in
824
+ acc[word, default: 0] += 1
825
+ }
826
+ ```
827
+
828
+ ### 9.5 Use lazy for Chained Transforms on Large Sequences
829
+
830
+ ```swift
831
+ // ❌ Wrong: each step allocates an intermediate array
832
+ let firstMatch = bigArray.map(expensive).filter(isValid).first
833
+
834
+ // ✅ Correct: lazy avoids intermediate arrays and stops early
835
+ let firstMatch = bigArray.lazy.map(expensive).filter(isValid).first
836
+ ```
837
+
838
+ ---
839
+
840
+ ## 10. Testing
841
+
842
+ ### 10.1 Arrange-Act-Assert with XCTest
843
+
844
+ ```swift
845
+ import XCTest
846
+ @testable import MyApp
847
+
848
+ final class PriceCalculatorTests: XCTestCase {
849
+ func testDiscountApplied() {
850
+ // Arrange
851
+ let calculator = PriceCalculator(discount: 0.1)
852
+ // Act
853
+ let total = calculator.total(for: 100)
854
+ // Assert
855
+ XCTAssertEqual(total, 90, accuracy: 0.001)
856
+ }
857
+ }
858
+ ```
859
+
860
+ ### 10.2 Testing async Code
861
+
862
+ ```swift
863
+ // ✅ Mark the test method async and await directly
864
+ func testFetchUser() async throws {
865
+ let service = UserService(client: MockClient())
866
+ let user = try await service.fetchUser(id: "42")
867
+ XCTAssertEqual(user.id, "42")
868
+ }
869
+
870
+ // ✅ Assert that an async call throws the expected error
871
+ func testFetchUserUnauthorized() async {
872
+ let service = UserService(client: UnauthorizedClient())
873
+ do {
874
+ _ = try await service.fetchUser(id: "42")
875
+ XCTFail("expected to throw")
876
+ } catch NetworkError.unauthorized {
877
+ // expected
878
+ } catch {
879
+ XCTFail("unexpected error: \(error)")
880
+ }
881
+ }
882
+ ```
883
+
884
+ ### 10.3 Inject Dependencies via Protocols
885
+
886
+ ```swift
887
+ // ✅ Depend on a protocol so tests can substitute a mock
888
+ protocol HTTPClient {
889
+ func get(_ url: URL) async throws -> Data
890
+ }
891
+
892
+ struct MockClient: HTTPClient {
893
+ var result: Result<Data, Error>
894
+ func get(_ url: URL) async throws -> Data {
895
+ try result.get()
896
+ }
897
+ }
898
+ ```
899
+
900
+ ### 10.4 Avoid Sleeps; Await Expectations or Values
901
+
902
+ ```swift
903
+ // ❌ Wrong: arbitrary sleep makes tests slow and flaky
904
+ func testCallback() {
905
+ var done = false
906
+ object.run { done = true }
907
+ Thread.sleep(forTimeInterval: 1)
908
+ XCTAssertTrue(done)
909
+ }
910
+
911
+ // ✅ Correct: use XCTestExpectation for callback APIs
912
+ func testCallback() {
913
+ let expectation = expectation(description: "callback fired")
914
+ object.run { expectation.fulfill() }
915
+ wait(for: [expectation], timeout: 1.0)
916
+ }
917
+
918
+ // ✅ Better: refactor to async and await the value directly
919
+ func testCallback() async {
920
+ let value = await object.run()
921
+ XCTAssertEqual(value, expected)
922
+ }
923
+ ```
924
+
925
+ ---
926
+
927
+ ## References
928
+
929
+ - [Swift API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/)
930
+ - [The Swift Programming Language](https://docs.swift.org/swift-book/)
931
+ - [Swift Concurrency (TSPL)](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/)
932
+ - [Migrating to Swift 6](https://www.swift.org/migration/documentation/migrationguide/)
933
+ - [Apple: Managing Model Data in Your App (SwiftUI)](https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app)
934
+ - [Apple: Automatic Reference Counting](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/)
935
+ - [WWDC: Protocol-Oriented Programming in Swift](https://developer.apple.com/videos/play/wwdc2015/408/)
936
+ - [Swift Evolution](https://github.com/apple/swift-evolution)