swift-code-reviewer-skill 1.0.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.
- package/CHANGELOG.md +214 -0
- package/CONTRIBUTING.md +271 -0
- package/LICENSE +21 -0
- package/README.md +536 -0
- package/SKILL.md +690 -0
- package/bin/install.js +173 -0
- package/package.json +41 -0
- package/references/architecture-patterns.md +862 -0
- package/references/custom-guidelines.md +852 -0
- package/references/feedback-templates.md +666 -0
- package/references/performance-review.md +914 -0
- package/references/review-workflow.md +1131 -0
- package/references/security-checklist.md +781 -0
- package/references/swift-quality-checklist.md +928 -0
- package/references/swiftui-review-checklist.md +909 -0
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
# Swift Quality Checklist
|
|
2
|
+
|
|
3
|
+
This checklist covers Swift 6+ language features, concurrency patterns, error handling, optionals, access control, and naming conventions. Use this to ensure code follows modern Swift best practices.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Concurrency & Swift 6 Patterns
|
|
8
|
+
|
|
9
|
+
### 1.1 Actor Isolation
|
|
10
|
+
|
|
11
|
+
**Check for:**
|
|
12
|
+
- [ ] UI-related classes marked with `@MainActor`
|
|
13
|
+
- [ ] Mutable state properly isolated with actors
|
|
14
|
+
- [ ] No shared mutable state without synchronization
|
|
15
|
+
- [ ] Proper actor isolation boundaries
|
|
16
|
+
|
|
17
|
+
**Examples:**
|
|
18
|
+
|
|
19
|
+
❌ **Bad: No actor isolation**
|
|
20
|
+
```swift
|
|
21
|
+
class UserViewModel {
|
|
22
|
+
var users: [User] = [] // ❌ Mutable state without isolation
|
|
23
|
+
|
|
24
|
+
func loadUsers() {
|
|
25
|
+
// Can be called from any thread - data race!
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
✅ **Good: Proper MainActor isolation**
|
|
31
|
+
```swift
|
|
32
|
+
@MainActor
|
|
33
|
+
class UserViewModel: ObservableObject {
|
|
34
|
+
@Published var users: [User] = [] // ✅ MainActor-isolated
|
|
35
|
+
|
|
36
|
+
func loadUsers() async {
|
|
37
|
+
// Always runs on main actor
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
✅ **Good: Custom actor for background work**
|
|
43
|
+
```swift
|
|
44
|
+
actor DatabaseManager {
|
|
45
|
+
private var cache: [String: Data] = [:] // ✅ Actor-isolated
|
|
46
|
+
|
|
47
|
+
func save(_ data: Data, forKey key: String) {
|
|
48
|
+
cache[key] = data
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func fetch(forKey key: String) -> Data? {
|
|
52
|
+
return cache[key]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 1.2 Sendable Conformance
|
|
58
|
+
|
|
59
|
+
**Check for:**
|
|
60
|
+
- [ ] Types crossing actor boundaries conform to Sendable
|
|
61
|
+
- [ ] Value types (struct, enum) are implicitly Sendable
|
|
62
|
+
- [ ] Reference types explicitly marked with Sendable where appropriate
|
|
63
|
+
- [ ] Non-Sendable types properly isolated
|
|
64
|
+
|
|
65
|
+
**Examples:**
|
|
66
|
+
|
|
67
|
+
❌ **Bad: Non-Sendable type crossing actors**
|
|
68
|
+
```swift
|
|
69
|
+
class UserData { // ❌ Class without Sendable
|
|
70
|
+
var name: String
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
actor DataStore {
|
|
74
|
+
func save(_ data: UserData) { // ⚠️ Warning: non-Sendable type
|
|
75
|
+
// ...
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
✅ **Good: Sendable struct**
|
|
81
|
+
```swift
|
|
82
|
+
struct UserData: Sendable { // ✅ Value type is Sendable
|
|
83
|
+
let name: String
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
actor DataStore {
|
|
87
|
+
func save(_ data: UserData) { // ✅ OK
|
|
88
|
+
// ...
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
✅ **Good: Sendable reference type**
|
|
94
|
+
```swift
|
|
95
|
+
final class UserData: @unchecked Sendable { // ✅ Explicitly Sendable
|
|
96
|
+
private let lock = NSLock()
|
|
97
|
+
private var _name: String
|
|
98
|
+
|
|
99
|
+
var name: String {
|
|
100
|
+
lock.lock()
|
|
101
|
+
defer { lock.unlock() }
|
|
102
|
+
return _name
|
|
103
|
+
}
|
|
104
|
+
// Thread-safe implementation
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 1.3 Async/Await Patterns
|
|
109
|
+
|
|
110
|
+
**Check for:**
|
|
111
|
+
- [ ] Async/await used instead of completion handlers
|
|
112
|
+
- [ ] Structured concurrency (Task, TaskGroup) over unstructured
|
|
113
|
+
- [ ] Proper error propagation with async throws
|
|
114
|
+
- [ ] No blocking the main thread
|
|
115
|
+
|
|
116
|
+
**Examples:**
|
|
117
|
+
|
|
118
|
+
❌ **Bad: Completion handler**
|
|
119
|
+
```swift
|
|
120
|
+
func fetchUser(id: UUID, completion: @escaping (Result<User, Error>) -> Void) {
|
|
121
|
+
// Old pattern
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
✅ **Good: Async/await**
|
|
126
|
+
```swift
|
|
127
|
+
func fetchUser(id: UUID) async throws -> User {
|
|
128
|
+
let (data, _) = try await URLSession.shared.data(from: url)
|
|
129
|
+
return try JSONDecoder().decode(User.self, from: data)
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
❌ **Bad: Unstructured concurrency**
|
|
134
|
+
```swift
|
|
135
|
+
func loadData() {
|
|
136
|
+
Task { // ❌ Unstructured, no cancellation handling
|
|
137
|
+
await fetchUsers()
|
|
138
|
+
}
|
|
139
|
+
Task {
|
|
140
|
+
await fetchPosts()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
✅ **Good: Structured concurrency**
|
|
146
|
+
```swift
|
|
147
|
+
func loadData() async {
|
|
148
|
+
await withTaskGroup(of: Void.self) { group in
|
|
149
|
+
group.addTask { await self.fetchUsers() }
|
|
150
|
+
group.addTask { await self.fetchPosts() }
|
|
151
|
+
// All tasks complete before function returns
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 1.4 Data Race Prevention
|
|
157
|
+
|
|
158
|
+
**Check for:**
|
|
159
|
+
- [ ] No mutable global state
|
|
160
|
+
- [ ] No mutable static properties (use actors or @MainActor)
|
|
161
|
+
- [ ] No unsynchronized shared state between actors
|
|
162
|
+
- [ ] Proper use of Task-local values
|
|
163
|
+
|
|
164
|
+
**Examples:**
|
|
165
|
+
|
|
166
|
+
❌ **Bad: Mutable global state**
|
|
167
|
+
```swift
|
|
168
|
+
var currentUser: User? // ❌ Mutable global - data race!
|
|
169
|
+
|
|
170
|
+
func updateUser(_ user: User) {
|
|
171
|
+
currentUser = user // ❌ Can be called from multiple threads
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
✅ **Good: Actor-isolated state**
|
|
176
|
+
```swift
|
|
177
|
+
@MainActor
|
|
178
|
+
final class UserManager {
|
|
179
|
+
static let shared = UserManager()
|
|
180
|
+
private(set) var currentUser: User? // ✅ MainActor-isolated
|
|
181
|
+
|
|
182
|
+
func updateUser(_ user: User) {
|
|
183
|
+
currentUser = user // ✅ Always on main actor
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
✅ **Good: Task-local value**
|
|
189
|
+
```swift
|
|
190
|
+
enum RequestID {
|
|
191
|
+
@TaskLocal static var current: UUID?
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
func processRequest() async {
|
|
195
|
+
await RequestID.$current.withValue(UUID()) {
|
|
196
|
+
await handleRequest() // Has access to RequestID.current
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 1.5 Migration to Swift 6
|
|
202
|
+
|
|
203
|
+
**Check for:**
|
|
204
|
+
- [ ] Gradual migration approach (per-module or per-file)
|
|
205
|
+
- [ ] Swift 6 language mode enabled incrementally
|
|
206
|
+
- [ ] Concurrency warnings addressed
|
|
207
|
+
- [ ] Deprecated APIs replaced
|
|
208
|
+
|
|
209
|
+
**Migration Checklist:**
|
|
210
|
+
```swift
|
|
211
|
+
// Enable Swift 6 mode in Build Settings
|
|
212
|
+
// SWIFT_VERSION = 6.0
|
|
213
|
+
|
|
214
|
+
// Or per-file
|
|
215
|
+
// swift-tools-version: 6.0
|
|
216
|
+
|
|
217
|
+
// Check for warnings
|
|
218
|
+
// Build Settings > Swift Compiler > Code Generation
|
|
219
|
+
// Swift Language Version: Swift 6
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 2. Error Handling
|
|
225
|
+
|
|
226
|
+
### 2.1 Typed Throws (Swift 6+)
|
|
227
|
+
|
|
228
|
+
**Check for:**
|
|
229
|
+
- [ ] Typed throws for specific error types
|
|
230
|
+
- [ ] Error propagation without generic Error
|
|
231
|
+
- [ ] Meaningful error types
|
|
232
|
+
|
|
233
|
+
**Examples:**
|
|
234
|
+
|
|
235
|
+
❌ **Bad: Generic Error**
|
|
236
|
+
```swift
|
|
237
|
+
func fetchUser(id: UUID) throws -> User { // ❌ What errors can it throw?
|
|
238
|
+
// ...
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
✅ **Good: Typed throws**
|
|
243
|
+
```swift
|
|
244
|
+
enum UserError: Error {
|
|
245
|
+
case notFound
|
|
246
|
+
case invalidData
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
func fetchUser(id: UUID) throws(UserError) -> User { // ✅ Explicit error type
|
|
250
|
+
// ...
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Caller knows exactly what to catch
|
|
254
|
+
do {
|
|
255
|
+
let user = try fetchUser(id: userID)
|
|
256
|
+
} catch UserError.notFound {
|
|
257
|
+
// Handle not found
|
|
258
|
+
} catch UserError.invalidData {
|
|
259
|
+
// Handle invalid data
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 2.2 Result Type
|
|
264
|
+
|
|
265
|
+
**Check for:**
|
|
266
|
+
- [ ] Result type for recoverable errors in async contexts
|
|
267
|
+
- [ ] Proper success/failure handling
|
|
268
|
+
- [ ] Meaningful error types
|
|
269
|
+
|
|
270
|
+
**Examples:**
|
|
271
|
+
|
|
272
|
+
✅ **Good: Result type**
|
|
273
|
+
```swift
|
|
274
|
+
func fetchUser(id: UUID) async -> Result<User, NetworkError> {
|
|
275
|
+
do {
|
|
276
|
+
let user = try await networkService.fetch(User.self, id: id)
|
|
277
|
+
return .success(user)
|
|
278
|
+
} catch let error as NetworkError {
|
|
279
|
+
return .failure(error)
|
|
280
|
+
} catch {
|
|
281
|
+
return .failure(.unknown)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Usage
|
|
286
|
+
let result = await fetchUser(id: userID)
|
|
287
|
+
switch result {
|
|
288
|
+
case .success(let user):
|
|
289
|
+
// Handle success
|
|
290
|
+
case .failure(let error):
|
|
291
|
+
// Handle error
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 2.3 Force Try Audit
|
|
296
|
+
|
|
297
|
+
**Check for:**
|
|
298
|
+
- [ ] No `try!` (force try) unless absolutely justified
|
|
299
|
+
- [ ] Proper error handling with do-catch or Result
|
|
300
|
+
- [ ] Comments explaining any necessary force tries
|
|
301
|
+
|
|
302
|
+
**Examples:**
|
|
303
|
+
|
|
304
|
+
❌ **Bad: Force try**
|
|
305
|
+
```swift
|
|
306
|
+
let user = try! decoder.decode(User.self, from: data) // ❌ Can crash!
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
✅ **Good: Proper error handling**
|
|
310
|
+
```swift
|
|
311
|
+
do {
|
|
312
|
+
let user = try decoder.decode(User.self, from: data)
|
|
313
|
+
return user
|
|
314
|
+
} catch {
|
|
315
|
+
logger.error("Failed to decode user: \(error)")
|
|
316
|
+
return nil
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
✅ **Acceptable: Force try with justification**
|
|
321
|
+
```swift
|
|
322
|
+
// Static JSON bundled with app - guaranteed to be valid
|
|
323
|
+
let defaultConfig = try! decoder.decode(
|
|
324
|
+
Config.self,
|
|
325
|
+
from: bundledJSONData
|
|
326
|
+
) // Force try justified: bundled resource
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### 2.4 Error Types
|
|
330
|
+
|
|
331
|
+
**Check for:**
|
|
332
|
+
- [ ] Custom error enums for domain-specific errors
|
|
333
|
+
- [ ] LocalizedError conformance for user-facing errors
|
|
334
|
+
- [ ] Descriptive error messages
|
|
335
|
+
|
|
336
|
+
**Examples:**
|
|
337
|
+
|
|
338
|
+
✅ **Good: Custom error enum**
|
|
339
|
+
```swift
|
|
340
|
+
enum LoginError: Error {
|
|
341
|
+
case invalidCredentials
|
|
342
|
+
case accountLocked
|
|
343
|
+
case networkFailure(underlying: Error)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
extension LoginError: LocalizedError {
|
|
347
|
+
var errorDescription: String? {
|
|
348
|
+
switch self {
|
|
349
|
+
case .invalidCredentials:
|
|
350
|
+
return "Invalid email or password"
|
|
351
|
+
case .accountLocked:
|
|
352
|
+
return "Your account has been locked. Please contact support."
|
|
353
|
+
case .networkFailure:
|
|
354
|
+
return "Network connection failed. Please try again."
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## 3. Optionals Handling
|
|
363
|
+
|
|
364
|
+
### 3.1 Force Unwrap Audit
|
|
365
|
+
|
|
366
|
+
**Check for:**
|
|
367
|
+
- [ ] No force unwrapping (`!`) unless absolutely justified
|
|
368
|
+
- [ ] No forced casting (`as!`)
|
|
369
|
+
- [ ] Comments explaining any necessary force unwraps
|
|
370
|
+
|
|
371
|
+
**Examples:**
|
|
372
|
+
|
|
373
|
+
❌ **Bad: Force unwrap**
|
|
374
|
+
```swift
|
|
375
|
+
let user = userRepository.currentUser! // ❌ Can crash!
|
|
376
|
+
let name = user.name!
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
✅ **Good: Guard statement**
|
|
380
|
+
```swift
|
|
381
|
+
guard let user = userRepository.currentUser else {
|
|
382
|
+
logger.error("No current user")
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
let name = user.name ?? "Unknown"
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
✅ **Good: If-let binding**
|
|
389
|
+
```swift
|
|
390
|
+
if let user = userRepository.currentUser {
|
|
391
|
+
displayUser(user)
|
|
392
|
+
} else {
|
|
393
|
+
showLoginScreen()
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
✅ **Good: Optional chaining**
|
|
398
|
+
```swift
|
|
399
|
+
let username = userRepository.currentUser?.name ?? "Guest"
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 3.2 Implicitly Unwrapped Optionals
|
|
403
|
+
|
|
404
|
+
**Check for:**
|
|
405
|
+
- [ ] Avoid `!` declarations unless absolutely necessary
|
|
406
|
+
- [ ] Use regular optionals with proper unwrapping
|
|
407
|
+
- [ ] Only use IUO for IBOutlets or when guaranteed to be set
|
|
408
|
+
|
|
409
|
+
**Examples:**
|
|
410
|
+
|
|
411
|
+
❌ **Bad: Unnecessary IUO**
|
|
412
|
+
```swift
|
|
413
|
+
class UserViewModel {
|
|
414
|
+
var authService: AuthService! // ❌ Why IUO?
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
✅ **Good: Regular optional or non-optional**
|
|
419
|
+
```swift
|
|
420
|
+
class UserViewModel {
|
|
421
|
+
let authService: AuthService // ✅ Non-optional with DI
|
|
422
|
+
|
|
423
|
+
init(authService: AuthService) {
|
|
424
|
+
self.authService = authService
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
✅ **Acceptable: IBOutlet**
|
|
430
|
+
```swift
|
|
431
|
+
class LoginViewController: UIViewController {
|
|
432
|
+
@IBOutlet weak var emailTextField: UITextField! // ✅ Acceptable for IB
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### 3.3 Nil Coalescing
|
|
437
|
+
|
|
438
|
+
**Check for:**
|
|
439
|
+
- [ ] Appropriate use of ?? operator
|
|
440
|
+
- [ ] Meaningful default values
|
|
441
|
+
- [ ] No force unwrapping when ?? can be used
|
|
442
|
+
|
|
443
|
+
**Examples:**
|
|
444
|
+
|
|
445
|
+
✅ **Good: Nil coalescing**
|
|
446
|
+
```swift
|
|
447
|
+
let username = user.name ?? "Guest"
|
|
448
|
+
let count = items?.count ?? 0
|
|
449
|
+
let config = loadConfig() ?? Config.default
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## 4. Access Control
|
|
455
|
+
|
|
456
|
+
### 4.1 Explicit Access Control
|
|
457
|
+
|
|
458
|
+
**Check for:**
|
|
459
|
+
- [ ] Explicit access control (not relying on defaults)
|
|
460
|
+
- [ ] Private for implementation details
|
|
461
|
+
- [ ] Internal for module-level sharing
|
|
462
|
+
- [ ] Public only for API surface
|
|
463
|
+
- [ ] File-private for file-scoped sharing
|
|
464
|
+
|
|
465
|
+
**Examples:**
|
|
466
|
+
|
|
467
|
+
❌ **Bad: Implicit access control**
|
|
468
|
+
```swift
|
|
469
|
+
struct User { // ❌ Implicit internal
|
|
470
|
+
var name: String // ❌ Implicit internal
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
✅ **Good: Explicit access control**
|
|
475
|
+
```swift
|
|
476
|
+
public struct User { // ✅ Explicit public
|
|
477
|
+
public let name: String // ✅ Explicit public
|
|
478
|
+
private let id: UUID // ✅ Explicit private
|
|
479
|
+
|
|
480
|
+
public init(name: String, id: UUID) { // ✅ Public init
|
|
481
|
+
self.name = name
|
|
482
|
+
self.id = id
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### 4.2 Minimizing API Surface
|
|
488
|
+
|
|
489
|
+
**Check for:**
|
|
490
|
+
- [ ] Private by default
|
|
491
|
+
- [ ] Only expose what's necessary
|
|
492
|
+
- [ ] Final classes when inheritance not needed
|
|
493
|
+
|
|
494
|
+
**Examples:**
|
|
495
|
+
|
|
496
|
+
✅ **Good: Minimal API surface**
|
|
497
|
+
```swift
|
|
498
|
+
public protocol AuthService {
|
|
499
|
+
func login(email: String, password: String) async throws -> User
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
internal final class DefaultAuthService: AuthService { // ✅ Internal, final
|
|
503
|
+
private let networkClient: NetworkClient // ✅ Private
|
|
504
|
+
private let tokenStorage: TokenStorage // ✅ Private
|
|
505
|
+
|
|
506
|
+
internal init(networkClient: NetworkClient, tokenStorage: TokenStorage) {
|
|
507
|
+
self.networkClient = networkClient
|
|
508
|
+
self.tokenStorage = tokenStorage
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
public func login(email: String, password: String) async throws -> User {
|
|
512
|
+
// Implementation
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private func validateCredentials(email: String, password: String) -> Bool {
|
|
516
|
+
// ✅ Private helper
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### 4.3 @testable Import
|
|
522
|
+
|
|
523
|
+
**Check for:**
|
|
524
|
+
- [ ] Internal types testable via @testable import
|
|
525
|
+
- [ ] No need to make things public for testing
|
|
526
|
+
- [ ] Proper test target configuration
|
|
527
|
+
|
|
528
|
+
**Examples:**
|
|
529
|
+
|
|
530
|
+
✅ **Good: Internal types with @testable**
|
|
531
|
+
```swift
|
|
532
|
+
// In main target
|
|
533
|
+
internal final class UserViewModel { // ✅ Internal
|
|
534
|
+
internal func fetchUsers() async { // ✅ Internal
|
|
535
|
+
// Implementation
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// In test target
|
|
540
|
+
@testable import MyApp
|
|
541
|
+
|
|
542
|
+
final class UserViewModelTests: XCTestCase {
|
|
543
|
+
func testFetchUsers() async {
|
|
544
|
+
let viewModel = UserViewModel() // ✅ Accessible via @testable
|
|
545
|
+
await viewModel.fetchUsers()
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## 5. Naming Conventions
|
|
553
|
+
|
|
554
|
+
### 5.1 Swift API Design Guidelines
|
|
555
|
+
|
|
556
|
+
**Check for:**
|
|
557
|
+
- [ ] Clear, descriptive names
|
|
558
|
+
- [ ] Proper parameter labels (argument labels + parameter names)
|
|
559
|
+
- [ ] Methods start with verbs
|
|
560
|
+
- [ ] Bool properties start with `is`, `has`, `should`
|
|
561
|
+
- [ ] Types use UpperCamelCase
|
|
562
|
+
- [ ] Properties/variables use lowerCamelCase
|
|
563
|
+
|
|
564
|
+
**Examples:**
|
|
565
|
+
|
|
566
|
+
❌ **Bad: Poor naming**
|
|
567
|
+
```swift
|
|
568
|
+
func get(id: UUID) -> User // ❌ Unclear
|
|
569
|
+
func userFetch(uuid: UUID) -> User // ❌ Non-standard
|
|
570
|
+
var loading: Bool // ❌ Unclear
|
|
571
|
+
let max_retry: Int // ❌ Snake case
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
✅ **Good: Swift API Design Guidelines**
|
|
575
|
+
```swift
|
|
576
|
+
func fetchUser(withID id: UUID) async throws -> User // ✅ Clear, verb-based
|
|
577
|
+
var isLoading: Bool // ✅ Bool prefix
|
|
578
|
+
let maximumRetryCount: Int // ✅ Descriptive, camelCase
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### 5.2 Argument Labels
|
|
582
|
+
|
|
583
|
+
**Check for:**
|
|
584
|
+
- [ ] Fluent usage at call site
|
|
585
|
+
- [ ] Argument labels clarify purpose
|
|
586
|
+
- [ ] Omit labels when context is clear
|
|
587
|
+
|
|
588
|
+
**Examples:**
|
|
589
|
+
|
|
590
|
+
❌ **Bad: Unclear at call site**
|
|
591
|
+
```swift
|
|
592
|
+
func validate(_ email: String, _ password: String) -> Bool
|
|
593
|
+
// Usage: validate(email, password) // ❌ Unclear
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
✅ **Good: Clear argument labels**
|
|
597
|
+
```swift
|
|
598
|
+
func validate(email: String, password: String) -> Bool
|
|
599
|
+
// Usage: validate(email: userEmail, password: userPassword) // ✅ Clear
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
✅ **Good: Fluent external labels**
|
|
603
|
+
```swift
|
|
604
|
+
func move(from start: Point, to end: Point)
|
|
605
|
+
// Usage: move(from: origin, to: destination) // ✅ Reads like English
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### 5.3 Type Naming
|
|
609
|
+
|
|
610
|
+
**Check for:**
|
|
611
|
+
- [ ] Noun-based type names
|
|
612
|
+
- [ ] Protocols describe capabilities (-able, -ing)
|
|
613
|
+
- [ ] Clear, non-abbreviated names
|
|
614
|
+
|
|
615
|
+
**Examples:**
|
|
616
|
+
|
|
617
|
+
✅ **Good: Type naming**
|
|
618
|
+
```swift
|
|
619
|
+
struct User { } // ✅ Noun
|
|
620
|
+
class UserViewModel { } // ✅ Descriptive
|
|
621
|
+
protocol Authenticating { } // ✅ Capability
|
|
622
|
+
protocol DataStorage { } // ✅ Capability
|
|
623
|
+
enum NetworkError { } // ✅ Descriptive
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
## 6. Type Inference vs Explicit Types
|
|
629
|
+
|
|
630
|
+
### 6.1 When to Use Type Inference
|
|
631
|
+
|
|
632
|
+
**Check for:**
|
|
633
|
+
- [ ] Type inference for obvious types
|
|
634
|
+
- [ ] Explicit types for clarity
|
|
635
|
+
- [ ] Explicit types for public APIs
|
|
636
|
+
|
|
637
|
+
**Examples:**
|
|
638
|
+
|
|
639
|
+
✅ **Good: Type inference**
|
|
640
|
+
```swift
|
|
641
|
+
let username = "john@example.com" // ✅ Obviously String
|
|
642
|
+
let count = items.count // ✅ Obviously Int
|
|
643
|
+
let viewModel = LoginViewModel() // ✅ Clear from initializer
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
✅ **Good: Explicit types for clarity**
|
|
647
|
+
```swift
|
|
648
|
+
let timeout: TimeInterval = 30 // ✅ Clarifies unit (seconds)
|
|
649
|
+
let coordinates: (latitude: Double, longitude: Double) = (37.7749, -122.4194)
|
|
650
|
+
let handler: ((Result<User, Error>) -> Void)? = nil // ✅ Complex type
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
✅ **Good: Explicit types in public APIs**
|
|
654
|
+
```swift
|
|
655
|
+
public func fetchUser(id: UUID) async throws -> User { // ✅ Explicit return
|
|
656
|
+
// Implementation
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## 7. Value Types vs Reference Types
|
|
663
|
+
|
|
664
|
+
### 7.1 Struct vs Class
|
|
665
|
+
|
|
666
|
+
**Check for:**
|
|
667
|
+
- [ ] Structs for value semantics (data models)
|
|
668
|
+
- [ ] Classes for identity and reference semantics
|
|
669
|
+
- [ ] Actors for concurrent mutable state
|
|
670
|
+
- [ ] Protocols for abstraction
|
|
671
|
+
|
|
672
|
+
**Examples:**
|
|
673
|
+
|
|
674
|
+
✅ **Good: Struct for data**
|
|
675
|
+
```swift
|
|
676
|
+
struct User { // ✅ Value type for data
|
|
677
|
+
let id: UUID
|
|
678
|
+
let name: String
|
|
679
|
+
let email: String
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
✅ **Good: Class for identity**
|
|
684
|
+
```swift
|
|
685
|
+
@MainActor
|
|
686
|
+
final class UserViewModel: ObservableObject { // ✅ Reference type for state
|
|
687
|
+
@Published var users: [User] = []
|
|
688
|
+
}
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
✅ **Good: Actor for concurrent state**
|
|
692
|
+
```swift
|
|
693
|
+
actor DatabaseManager { // ✅ Actor for thread-safe mutable state
|
|
694
|
+
private var cache: [UUID: User] = [:]
|
|
695
|
+
}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### 7.2 Copy-on-Write
|
|
699
|
+
|
|
700
|
+
**Check for:**
|
|
701
|
+
- [ ] Structs with large data use copy-on-write
|
|
702
|
+
- [ ] Custom copy-on-write for performance-critical code
|
|
703
|
+
|
|
704
|
+
**Examples:**
|
|
705
|
+
|
|
706
|
+
✅ **Good: Copy-on-write for large data**
|
|
707
|
+
```swift
|
|
708
|
+
struct LargeDataSet {
|
|
709
|
+
private var storage: Storage // ✅ Reference type storage
|
|
710
|
+
|
|
711
|
+
private final class Storage {
|
|
712
|
+
var data: [Int]
|
|
713
|
+
init(data: [Int]) { self.data = data }
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
mutating func append(_ value: Int) {
|
|
717
|
+
if !isKnownUniquelyReferenced(&storage) {
|
|
718
|
+
storage = Storage(data: storage.data) // ✅ Copy on write
|
|
719
|
+
}
|
|
720
|
+
storage.data.append(value)
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
---
|
|
726
|
+
|
|
727
|
+
## 8. Property Wrappers
|
|
728
|
+
|
|
729
|
+
### 8.1 Built-in Property Wrappers
|
|
730
|
+
|
|
731
|
+
**Check for:**
|
|
732
|
+
- [ ] Appropriate use of property wrappers
|
|
733
|
+
- [ ] No misuse or overuse
|
|
734
|
+
|
|
735
|
+
**Common Property Wrappers:**
|
|
736
|
+
|
|
737
|
+
| Property Wrapper | Use Case |
|
|
738
|
+
|-----------------|----------|
|
|
739
|
+
| `@Published` | Observable properties (with ObservableObject) |
|
|
740
|
+
| `@State` | View-local state (SwiftUI) |
|
|
741
|
+
| `@Binding` | Two-way binding (SwiftUI) |
|
|
742
|
+
| `@Environment` | Dependency injection (SwiftUI) |
|
|
743
|
+
| `@AppStorage` | UserDefaults-backed properties |
|
|
744
|
+
| `@MainActor` | Main thread isolation |
|
|
745
|
+
| `@TaskLocal` | Task-local values |
|
|
746
|
+
|
|
747
|
+
**Examples:**
|
|
748
|
+
|
|
749
|
+
✅ **Good: Appropriate property wrapper use**
|
|
750
|
+
```swift
|
|
751
|
+
@MainActor
|
|
752
|
+
final class UserViewModel: ObservableObject {
|
|
753
|
+
@Published var users: [User] = [] // ✅ For ObservableObject
|
|
754
|
+
@Published var isLoading: Bool = false
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
struct UserView: View {
|
|
758
|
+
@State private var selectedUser: User? // ✅ View-local state
|
|
759
|
+
@Environment(\.dismiss) private var dismiss // ✅ Environment
|
|
760
|
+
}
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
## 9. Code Organization
|
|
766
|
+
|
|
767
|
+
### 9.1 MARK Comments
|
|
768
|
+
|
|
769
|
+
**Check for:**
|
|
770
|
+
- [ ] MARK comments for logical sections
|
|
771
|
+
- [ ] Consistent section ordering
|
|
772
|
+
- [ ] Separation of protocol conformances
|
|
773
|
+
|
|
774
|
+
**Examples:**
|
|
775
|
+
|
|
776
|
+
✅ **Good: Organized with MARK**
|
|
777
|
+
```swift
|
|
778
|
+
final class UserViewModel {
|
|
779
|
+
// MARK: - Properties
|
|
780
|
+
private let authService: AuthService
|
|
781
|
+
@Published var users: [User] = []
|
|
782
|
+
|
|
783
|
+
// MARK: - Initialization
|
|
784
|
+
init(authService: AuthService) {
|
|
785
|
+
self.authService = authService
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// MARK: - Public Methods
|
|
789
|
+
func fetchUsers() async {
|
|
790
|
+
// Implementation
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// MARK: - Private Methods
|
|
794
|
+
private func parseUsers(_ data: Data) -> [User] {
|
|
795
|
+
// Implementation
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// MARK: - Equatable
|
|
800
|
+
extension UserViewModel: Equatable {
|
|
801
|
+
static func == (lhs: UserViewModel, rhs: UserViewModel) -> Bool {
|
|
802
|
+
// Implementation
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
### 9.2 Extensions for Protocol Conformances
|
|
808
|
+
|
|
809
|
+
**Check for:**
|
|
810
|
+
- [ ] Protocol conformances in separate extensions
|
|
811
|
+
- [ ] Logical grouping of related functionality
|
|
812
|
+
|
|
813
|
+
**Examples:**
|
|
814
|
+
|
|
815
|
+
✅ **Good: Extensions for protocols**
|
|
816
|
+
```swift
|
|
817
|
+
struct User {
|
|
818
|
+
let id: UUID
|
|
819
|
+
let name: String
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// MARK: - Identifiable
|
|
823
|
+
extension User: Identifiable { }
|
|
824
|
+
|
|
825
|
+
// MARK: - Codable
|
|
826
|
+
extension User: Codable { }
|
|
827
|
+
|
|
828
|
+
// MARK: - Equatable
|
|
829
|
+
extension User: Equatable { }
|
|
830
|
+
|
|
831
|
+
// MARK: - CustomStringConvertible
|
|
832
|
+
extension User: CustomStringConvertible {
|
|
833
|
+
var description: String {
|
|
834
|
+
"User(id: \(id), name: \(name))"
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
---
|
|
840
|
+
|
|
841
|
+
## 10. Modern Swift Features
|
|
842
|
+
|
|
843
|
+
### 10.1 Swift 6 Features
|
|
844
|
+
|
|
845
|
+
**Check for:**
|
|
846
|
+
- [ ] Use of new Swift 6 features where appropriate
|
|
847
|
+
- [ ] Typed throws
|
|
848
|
+
- [ ] Strict concurrency checking
|
|
849
|
+
- [ ] Noncopyable types (where applicable)
|
|
850
|
+
|
|
851
|
+
**Examples:**
|
|
852
|
+
|
|
853
|
+
✅ **Good: Typed throws**
|
|
854
|
+
```swift
|
|
855
|
+
func fetchUser(id: UUID) throws(NetworkError) -> User {
|
|
856
|
+
// Implementation
|
|
857
|
+
}
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
✅ **Good: Noncopyable types**
|
|
861
|
+
```swift
|
|
862
|
+
struct FileHandle: ~Copyable { // ✅ Swift 6: noncopyable
|
|
863
|
+
private let descriptor: Int32
|
|
864
|
+
|
|
865
|
+
consuming func close() {
|
|
866
|
+
// Close file descriptor
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
### 10.2 Availability Attributes
|
|
872
|
+
|
|
873
|
+
**Check for:**
|
|
874
|
+
- [ ] Availability attributes for new APIs
|
|
875
|
+
- [ ] Backward compatibility considerations
|
|
876
|
+
- [ ] Feature detection for platform-specific code
|
|
877
|
+
|
|
878
|
+
**Examples:**
|
|
879
|
+
|
|
880
|
+
✅ **Good: Availability attributes**
|
|
881
|
+
```swift
|
|
882
|
+
@available(iOS 17.0, macOS 14.0, *)
|
|
883
|
+
func modernFeature() {
|
|
884
|
+
// Use iOS 17+ APIs
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
func compatibleFunction() {
|
|
888
|
+
if #available(iOS 17.0, *) {
|
|
889
|
+
modernFeature()
|
|
890
|
+
} else {
|
|
891
|
+
// Fallback implementation
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
---
|
|
897
|
+
|
|
898
|
+
## Quick Reference Checklist
|
|
899
|
+
|
|
900
|
+
### Critical Issues
|
|
901
|
+
- [ ] No data races (shared mutable state)
|
|
902
|
+
- [ ] No force unwraps (`!`, `as!`, `try!`)
|
|
903
|
+
- [ ] Proper actor isolation
|
|
904
|
+
- [ ] MainActor for UI code
|
|
905
|
+
|
|
906
|
+
### High Priority
|
|
907
|
+
- [ ] Async/await instead of completion handlers
|
|
908
|
+
- [ ] Typed throws for error handling
|
|
909
|
+
- [ ] Sendable conformance for types crossing actors
|
|
910
|
+
- [ ] Explicit access control
|
|
911
|
+
|
|
912
|
+
### Medium Priority
|
|
913
|
+
- [ ] Proper naming conventions
|
|
914
|
+
- [ ] MARK comments for organization
|
|
915
|
+
- [ ] Protocol conformances in extensions
|
|
916
|
+
- [ ] Meaningful error types
|
|
917
|
+
|
|
918
|
+
### Low Priority
|
|
919
|
+
- [ ] Type inference vs explicit types
|
|
920
|
+
- [ ] Comments for complex logic
|
|
921
|
+
- [ ] Consistent code style
|
|
922
|
+
|
|
923
|
+
---
|
|
924
|
+
|
|
925
|
+
## Version
|
|
926
|
+
**Last Updated**: 2026-02-10
|
|
927
|
+
**Version**: 1.0.0
|
|
928
|
+
**Swift Version**: 6.0+
|