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.
@@ -0,0 +1,862 @@
1
+ # Architecture Patterns Guide
2
+
3
+ This guide covers common architectural patterns for Swift and SwiftUI applications, including MVVM, MVI, TCA, dependency injection, testing strategies, and code organization principles.
4
+
5
+ ---
6
+
7
+ ## 1. MVVM (Model-View-ViewModel)
8
+
9
+ ### 1.1 Overview
10
+
11
+ **Structure:**
12
+ - **View**: SwiftUI views (presentation only)
13
+ - **ViewModel**: Business logic and state management
14
+ - **Model**: Data structures and domain logic
15
+
16
+ **Responsibilities:**
17
+
18
+ | Layer | Responsibilities | Does NOT Do |
19
+ |-------|-----------------|-------------|
20
+ | **View** | - Display data<br>- User interactions<br>- UI layout | - Business logic<br>- Data fetching<br>- Validation |
21
+ | **ViewModel** | - Business logic<br>- State management<br>- Data transformation<br>- Validation | - UI code<br>- View hierarchy<br>- Direct database/network |
22
+ | **Model** | - Data structures<br>- Domain logic<br>- Business rules | - UI concerns<br>- State management |
23
+
24
+ ### 1.2 Implementation Pattern
25
+
26
+ **Check for:**
27
+ - [ ] Views only contain presentation logic
28
+ - [ ] ViewModels contain all business logic
29
+ - [ ] Clear separation between View and ViewModel
30
+ - [ ] Dependency injection for services
31
+
32
+ **Examples:**
33
+
34
+ ❌ **Bad: Business logic in view**
35
+ ```swift
36
+ struct UserListView: View {
37
+ @State private var users: [User] = []
38
+ @State private var isLoading = false
39
+
40
+ var body: some View {
41
+ List(users) { user in
42
+ UserRow(user: user)
43
+ }
44
+ .onAppear {
45
+ loadUsers() // ❌ Business logic in view
46
+ }
47
+ }
48
+
49
+ private func loadUsers() {
50
+ isLoading = true
51
+ Task {
52
+ // ❌ Network call directly in view
53
+ let response = try await URLSession.shared.data(from: usersURL)
54
+ users = try JSONDecoder().decode([User].self, from: response.0)
55
+ isLoading = false
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ✅ **Good: MVVM structure**
62
+ ```swift
63
+ // Model
64
+ struct User: Identifiable, Codable {
65
+ let id: UUID
66
+ let name: String
67
+ let email: String
68
+ }
69
+
70
+ // ViewModel
71
+ @MainActor
72
+ @Observable
73
+ final class UserListViewModel {
74
+ private let userRepository: UserRepository
75
+
76
+ private(set) var users: [User] = []
77
+ private(set) var isLoading: Bool = false
78
+ private(set) var error: Error?
79
+
80
+ init(userRepository: UserRepository) {
81
+ self.userRepository = userRepository
82
+ }
83
+
84
+ func loadUsers() async {
85
+ isLoading = true
86
+ error = nil
87
+
88
+ do {
89
+ users = try await userRepository.fetchUsers()
90
+ } catch {
91
+ self.error = error
92
+ }
93
+
94
+ isLoading = false
95
+ }
96
+ }
97
+
98
+ // View
99
+ struct UserListView: View {
100
+ let viewModel: UserListViewModel
101
+
102
+ var body: some View {
103
+ List(viewModel.users) { user in
104
+ UserRow(user: user)
105
+ }
106
+ .overlay {
107
+ if viewModel.isLoading {
108
+ ProgressView()
109
+ }
110
+ }
111
+ .task {
112
+ await viewModel.loadUsers() // ✅ View triggers, ViewModel handles
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### 1.3 ViewModel Best Practices
119
+
120
+ **Check for:**
121
+ - [ ] ViewModels are @Observable (iOS 17+) or ObservableObject (iOS 16-)
122
+ - [ ] @MainActor for UI-related ViewModels
123
+ - [ ] Services injected via initializer
124
+ - [ ] ViewModels are testable (protocol-based dependencies)
125
+
126
+ ---
127
+
128
+ ## 2. Repository Pattern
129
+
130
+ ### 2.1 Overview
131
+
132
+ **Purpose**: Abstracts data sources (network, database, cache) behind a clean interface
133
+
134
+ **Structure:**
135
+ - **Repository Protocol**: Defines data operations
136
+ - **Repository Implementation**: Implements protocol using data sources
137
+ - **Data Sources**: Network client, database, cache
138
+
139
+ ### 2.2 Implementation Pattern
140
+
141
+ **Check for:**
142
+ - [ ] Repository protocols for abstraction
143
+ - [ ] Multiple data sources coordinated
144
+ - [ ] Caching strategy implemented
145
+ - [ ] Error handling at repository level
146
+
147
+ **Examples:**
148
+
149
+ ✅ **Good: Repository pattern**
150
+ ```swift
151
+ // Repository Protocol
152
+ protocol UserRepository {
153
+ func fetchUsers() async throws -> [User]
154
+ func fetchUser(id: UUID) async throws -> User
155
+ func saveUser(_ user: User) async throws
156
+ func deleteUser(id: UUID) async throws
157
+ }
158
+
159
+ // Repository Implementation
160
+ final class DefaultUserRepository: UserRepository {
161
+ private let networkClient: NetworkClient
162
+ private let database: Database
163
+ private let cache: Cache
164
+
165
+ init(
166
+ networkClient: NetworkClient,
167
+ database: Database,
168
+ cache: Cache
169
+ ) {
170
+ self.networkClient = networkClient
171
+ self.database = database
172
+ self.cache = cache
173
+ }
174
+
175
+ func fetchUsers() async throws -> [User] {
176
+ // Check cache first
177
+ if let cached = cache.users, !cached.isEmpty {
178
+ return cached
179
+ }
180
+
181
+ // Fetch from network
182
+ let users = try await networkClient.fetchUsers()
183
+
184
+ // Save to database and cache
185
+ try await database.save(users)
186
+ cache.users = users
187
+
188
+ return users
189
+ }
190
+
191
+ func fetchUser(id: UUID) async throws -> User {
192
+ // Check cache
193
+ if let cached = cache.user(id: id) {
194
+ return cached
195
+ }
196
+
197
+ // Check database
198
+ if let local = try await database.fetchUser(id: id) {
199
+ cache.setUser(local)
200
+ return local
201
+ }
202
+
203
+ // Fetch from network
204
+ let user = try await networkClient.fetchUser(id: id)
205
+
206
+ // Save locally
207
+ try await database.save(user)
208
+ cache.setUser(user)
209
+
210
+ return user
211
+ }
212
+
213
+ func saveUser(_ user: User) async throws {
214
+ // Save to network first
215
+ try await networkClient.saveUser(user)
216
+
217
+ // Then save locally
218
+ try await database.save(user)
219
+ cache.setUser(user)
220
+ }
221
+
222
+ func deleteUser(id: UUID) async throws {
223
+ // Delete from network
224
+ try await networkClient.deleteUser(id: id)
225
+
226
+ // Delete locally
227
+ try await database.deleteUser(id: id)
228
+ cache.removeUser(id: id)
229
+ }
230
+ }
231
+ ```
232
+
233
+ ---
234
+
235
+ ## 3. Dependency Injection
236
+
237
+ ### 3.1 Constructor Injection
238
+
239
+ **Check for:**
240
+ - [ ] Dependencies passed via initializer
241
+ - [ ] Protocol-based dependencies (testable)
242
+ - [ ] No service locator or singletons
243
+
244
+ **Examples:**
245
+
246
+ ❌ **Bad: Singleton dependency**
247
+ ```swift
248
+ final class UserViewModel {
249
+ func loadUsers() async {
250
+ // ❌ Hard dependency on singleton
251
+ let users = try await NetworkService.shared.fetchUsers()
252
+ }
253
+ }
254
+ ```
255
+
256
+ ✅ **Good: Constructor injection**
257
+ ```swift
258
+ final class UserViewModel {
259
+ private let userRepository: UserRepository // ✅ Protocol
260
+
261
+ init(userRepository: UserRepository) { // ✅ Injected
262
+ self.userRepository = userRepository
263
+ }
264
+
265
+ func loadUsers() async {
266
+ let users = try await userRepository.fetchUsers()
267
+ }
268
+ }
269
+
270
+ // Usage
271
+ let viewModel = UserViewModel(
272
+ userRepository: DefaultUserRepository(
273
+ networkClient: networkClient,
274
+ database: database,
275
+ cache: cache
276
+ )
277
+ )
278
+ ```
279
+
280
+ ### 3.2 Environment-Based Injection (SwiftUI)
281
+
282
+ **Check for:**
283
+ - [ ] Custom environment values for dependencies
284
+ - [ ] Environment values for cross-cutting concerns
285
+ - [ ] Proper dependency scoping
286
+
287
+ **Examples:**
288
+
289
+ ✅ **Good: Environment injection**
290
+ ```swift
291
+ // Define environment key
292
+ private struct UserRepositoryKey: EnvironmentKey {
293
+ static let defaultValue: UserRepository = MockUserRepository()
294
+ }
295
+
296
+ extension EnvironmentValues {
297
+ var userRepository: UserRepository {
298
+ get { self[UserRepositoryKey.self] }
299
+ set { self[UserRepositoryKey.self] = newValue }
300
+ }
301
+ }
302
+
303
+ // Provide in app
304
+ @main
305
+ struct MyApp: App {
306
+ let userRepository = DefaultUserRepository(...)
307
+
308
+ var body: some Scene {
309
+ WindowGroup {
310
+ ContentView()
311
+ .environment(\.userRepository, userRepository)
312
+ }
313
+ }
314
+ }
315
+
316
+ // Use in view
317
+ struct UserListView: View {
318
+ @Environment(\.userRepository) private var repository
319
+ @State private var users: [User] = []
320
+
321
+ var body: some View {
322
+ List(users) { user in
323
+ UserRow(user: user)
324
+ }
325
+ .task {
326
+ users = try await repository.fetchUsers()
327
+ }
328
+ }
329
+ }
330
+ ```
331
+
332
+ ### 3.3 Dependency Container
333
+
334
+ **Check for:**
335
+ - [ ] Centralized dependency registration
336
+ - [ ] Type-safe dependency resolution
337
+ - [ ] Proper scoping (singleton, transient, scoped)
338
+
339
+ **Examples:**
340
+
341
+ ✅ **Good: Simple dependency container**
342
+ ```swift
343
+ final class DependencyContainer {
344
+ static let shared = DependencyContainer()
345
+
346
+ // Singletons
347
+ lazy var networkClient: NetworkClient = {
348
+ DefaultNetworkClient(configuration: .default)
349
+ }()
350
+
351
+ lazy var database: Database = {
352
+ try! Database(path: databasePath)
353
+ }()
354
+
355
+ lazy var cache: Cache = {
356
+ InMemoryCache()
357
+ }()
358
+
359
+ // Factories
360
+ func makeUserRepository() -> UserRepository {
361
+ DefaultUserRepository(
362
+ networkClient: networkClient,
363
+ database: database,
364
+ cache: cache
365
+ )
366
+ }
367
+
368
+ func makeUserViewModel() -> UserListViewModel {
369
+ UserListViewModel(userRepository: makeUserRepository())
370
+ }
371
+ }
372
+
373
+ // Usage
374
+ let viewModel = DependencyContainer.shared.makeUserViewModel()
375
+ ```
376
+
377
+ ---
378
+
379
+ ## 4. Use Case / Interactor Pattern
380
+
381
+ ### 4.1 Overview
382
+
383
+ **Purpose**: Encapsulates a single business operation or use case
384
+
385
+ **Benefits:**
386
+ - Single Responsibility Principle
387
+ - Easy to test
388
+ - Reusable across multiple ViewModels
389
+ - Clear business logic separation
390
+
391
+ ### 4.2 Implementation Pattern
392
+
393
+ **Check for:**
394
+ - [ ] One use case per class
395
+ - [ ] Execute method for operation
396
+ - [ ] Dependencies injected
397
+ - [ ] Returns Result or throws
398
+
399
+ **Examples:**
400
+
401
+ ✅ **Good: Use case pattern**
402
+ ```swift
403
+ // Use case protocol
404
+ protocol UseCase {
405
+ associatedtype Input
406
+ associatedtype Output
407
+
408
+ func execute(_ input: Input) async throws -> Output
409
+ }
410
+
411
+ // Login use case
412
+ struct LoginUseCase: UseCase {
413
+ private let authRepository: AuthRepository
414
+ private let tokenStorage: TokenStorage
415
+
416
+ init(authRepository: AuthRepository, tokenStorage: TokenStorage) {
417
+ self.authRepository = authRepository
418
+ self.tokenStorage = tokenStorage
419
+ }
420
+
421
+ struct Input {
422
+ let email: String
423
+ let password: String
424
+ }
425
+
426
+ func execute(_ input: Input) async throws -> User {
427
+ // Validate input
428
+ guard validateEmail(input.email) else {
429
+ throw LoginError.invalidEmail
430
+ }
431
+
432
+ guard input.password.count >= 8 else {
433
+ throw LoginError.passwordTooShort
434
+ }
435
+
436
+ // Perform login
437
+ let response = try await authRepository.login(
438
+ email: input.email,
439
+ password: input.password
440
+ )
441
+
442
+ // Store token
443
+ try await tokenStorage.save(response.token)
444
+
445
+ return response.user
446
+ }
447
+
448
+ private func validateEmail(_ email: String) -> Bool {
449
+ // Email validation logic
450
+ true
451
+ }
452
+ }
453
+
454
+ // ViewModel using use case
455
+ @MainActor
456
+ @Observable
457
+ final class LoginViewModel {
458
+ private let loginUseCase: LoginUseCase
459
+
460
+ var email: String = ""
461
+ var password: String = ""
462
+ var isLoading: Bool = false
463
+ var error: Error?
464
+
465
+ init(loginUseCase: LoginUseCase) {
466
+ self.loginUseCase = loginUseCase
467
+ }
468
+
469
+ func login() async {
470
+ isLoading = true
471
+ error = nil
472
+
473
+ do {
474
+ let input = LoginUseCase.Input(email: email, password: password)
475
+ let user = try await loginUseCase.execute(input)
476
+ // Handle successful login
477
+ } catch {
478
+ self.error = error
479
+ }
480
+
481
+ isLoading = false
482
+ }
483
+ }
484
+ ```
485
+
486
+ ---
487
+
488
+ ## 5. Coordinator Pattern
489
+
490
+ ### 5.1 Overview
491
+
492
+ **Purpose**: Manages navigation flow and screen transitions
493
+
494
+ **Benefits:**
495
+ - Decouples navigation from views
496
+ - Centralized navigation logic
497
+ - Deep linking support
498
+ - Testing navigation flows
499
+
500
+ ### 5.2 Implementation Pattern
501
+
502
+ **Check for:**
503
+ - [ ] Coordinator manages navigation state
504
+ - [ ] Views don't handle navigation
505
+ - [ ] Type-safe navigation
506
+ - [ ] Support for deep linking
507
+
508
+ **Examples:**
509
+
510
+ ✅ **Good: Coordinator pattern**
511
+ ```swift
512
+ // Route definition
513
+ enum Route: Hashable {
514
+ case userList
515
+ case userDetail(User)
516
+ case userEdit(User)
517
+ case settings
518
+ }
519
+
520
+ // Coordinator
521
+ @MainActor
522
+ @Observable
523
+ final class AppCoordinator {
524
+ var navigationPath = NavigationPath()
525
+
526
+ func navigate(to route: Route) {
527
+ navigationPath.append(route)
528
+ }
529
+
530
+ func pop() {
531
+ guard !navigationPath.isEmpty else { return }
532
+ navigationPath.removeLast()
533
+ }
534
+
535
+ func popToRoot() {
536
+ navigationPath.removeLast(navigationPath.count)
537
+ }
538
+ }
539
+
540
+ // Root view with navigation
541
+ struct AppView: View {
542
+ @State private var coordinator = AppCoordinator()
543
+
544
+ var body: some View {
545
+ NavigationStack(path: $coordinator.navigationPath) {
546
+ UserListView(coordinator: coordinator)
547
+ .navigationDestination(for: Route.self) { route in
548
+ destination(for: route)
549
+ }
550
+ }
551
+ }
552
+
553
+ @ViewBuilder
554
+ private func destination(for route: Route) -> some View {
555
+ switch route {
556
+ case .userList:
557
+ UserListView(coordinator: coordinator)
558
+
559
+ case .userDetail(let user):
560
+ UserDetailView(user: user, coordinator: coordinator)
561
+
562
+ case .userEdit(let user):
563
+ UserEditView(user: user, coordinator: coordinator)
564
+
565
+ case .settings:
566
+ SettingsView(coordinator: coordinator)
567
+ }
568
+ }
569
+ }
570
+
571
+ // View using coordinator
572
+ struct UserListView: View {
573
+ let coordinator: AppCoordinator
574
+ @State private var users: [User] = []
575
+
576
+ var body: some View {
577
+ List(users) { user in
578
+ Button {
579
+ coordinator.navigate(to: .userDetail(user)) // ✅ Coordinator handles navigation
580
+ } label: {
581
+ UserRow(user: user)
582
+ }
583
+ }
584
+ .navigationTitle("Users")
585
+ .toolbar {
586
+ Button("Settings") {
587
+ coordinator.navigate(to: .settings)
588
+ }
589
+ }
590
+ }
591
+ }
592
+ ```
593
+
594
+ ---
595
+
596
+ ## 6. Testing Strategies
597
+
598
+ ### 6.1 Unit Testing
599
+
600
+ **Check for:**
601
+ - [ ] ViewModels are unit tested
602
+ - [ ] Use cases are unit tested
603
+ - [ ] Repositories are unit tested
604
+ - [ ] Mocks used for dependencies
605
+ - [ ] High code coverage (>80%)
606
+
607
+ **Examples:**
608
+
609
+ ✅ **Good: Unit tests**
610
+ ```swift
611
+ import XCTest
612
+ @testable import MyApp
613
+
614
+ final class LoginViewModelTests: XCTestCase {
615
+ private var mockAuthRepository: MockAuthRepository!
616
+ private var mockTokenStorage: MockTokenStorage!
617
+ private var loginUseCase: LoginUseCase!
618
+ private var viewModel: LoginViewModel!
619
+
620
+ @MainActor
621
+ override func setUp() {
622
+ super.setUp()
623
+
624
+ mockAuthRepository = MockAuthRepository()
625
+ mockTokenStorage = MockTokenStorage()
626
+
627
+ loginUseCase = LoginUseCase(
628
+ authRepository: mockAuthRepository,
629
+ tokenStorage: mockTokenStorage
630
+ )
631
+
632
+ viewModel = LoginViewModel(loginUseCase: loginUseCase)
633
+ }
634
+
635
+ @MainActor
636
+ func testSuccessfulLogin() async throws {
637
+ // Arrange
638
+ let expectedUser = User(id: UUID(), name: "John", email: "john@example.com")
639
+ mockAuthRepository.loginResult = .success(
640
+ LoginResponse(user: expectedUser, token: "token123")
641
+ )
642
+
643
+ viewModel.email = "john@example.com"
644
+ viewModel.password = "password123"
645
+
646
+ // Act
647
+ await viewModel.login()
648
+
649
+ // Assert
650
+ XCTAssertFalse(viewModel.isLoading)
651
+ XCTAssertNil(viewModel.error)
652
+ XCTAssertEqual(mockAuthRepository.loginCallCount, 1)
653
+ XCTAssertEqual(mockTokenStorage.savedToken, "token123")
654
+ }
655
+
656
+ @MainActor
657
+ func testLoginWithInvalidEmail() async {
658
+ // Arrange
659
+ viewModel.email = "invalid-email"
660
+ viewModel.password = "password123"
661
+
662
+ // Act
663
+ await viewModel.login()
664
+
665
+ // Assert
666
+ XCTAssertFalse(viewModel.isLoading)
667
+ XCTAssertNotNil(viewModel.error)
668
+ XCTAssertEqual(mockAuthRepository.loginCallCount, 0) // Not called
669
+ }
670
+ }
671
+
672
+ // Mock repository
673
+ final class MockAuthRepository: AuthRepository {
674
+ var loginResult: Result<LoginResponse, Error> = .failure(MockError.notImplemented)
675
+ var loginCallCount = 0
676
+
677
+ func login(email: String, password: String) async throws -> LoginResponse {
678
+ loginCallCount += 1
679
+ return try loginResult.get()
680
+ }
681
+ }
682
+
683
+ enum MockError: Error {
684
+ case notImplemented
685
+ }
686
+ ```
687
+
688
+ ### 6.2 UI Testing
689
+
690
+ **Check for:**
691
+ - [ ] Critical user flows tested
692
+ - [ ] Accessibility identifiers used
693
+ - [ ] Page Object pattern for organization
694
+ - [ ] Tests are maintainable
695
+
696
+ **Examples:**
697
+
698
+ ✅ **Good: UI tests**
699
+ ```swift
700
+ import XCTest
701
+
702
+ final class LoginUITests: XCTestCase {
703
+ private var app: XCUIApplication!
704
+
705
+ override func setUpWithError() throws {
706
+ continueAfterFailure = false
707
+ app = XCUIApplication()
708
+ app.launch()
709
+ }
710
+
711
+ func testLoginFlow() {
712
+ // Arrange
713
+ let emailField = app.textFields["loginEmailField"]
714
+ let passwordField = app.secureTextFields["loginPasswordField"]
715
+ let loginButton = app.buttons["loginButton"]
716
+
717
+ // Act
718
+ emailField.tap()
719
+ emailField.typeText("john@example.com")
720
+
721
+ passwordField.tap()
722
+ passwordField.typeText("password123")
723
+
724
+ loginButton.tap()
725
+
726
+ // Assert
727
+ XCTAssertTrue(app.navigationBars["Home"].waitForExistence(timeout: 5))
728
+ }
729
+ }
730
+ ```
731
+
732
+ ### 6.3 Integration Testing
733
+
734
+ **Check for:**
735
+ - [ ] Repository + network integration tested
736
+ - [ ] Database operations tested
737
+ - [ ] End-to-end flows tested
738
+ - [ ] Real dependencies used (not mocks)
739
+
740
+ ---
741
+
742
+ ## 7. Code Organization
743
+
744
+ ### 7.1 File Structure
745
+
746
+ **Check for:**
747
+ - [ ] Logical folder organization
748
+ - [ ] Feature-based grouping
749
+ - [ ] Clear separation of concerns
750
+ - [ ] Consistent naming
751
+
752
+ **Example Structure:**
753
+ ```
754
+ MyApp/
755
+ ├── App/
756
+ │ ├── MyApp.swift
757
+ │ └── AppDelegate.swift
758
+ ├── Core/
759
+ │ ├── Network/
760
+ │ │ ├── NetworkClient.swift
761
+ │ │ └── APIEndpoint.swift
762
+ │ ├── Database/
763
+ │ │ └── Database.swift
764
+ │ └── DependencyInjection/
765
+ │ └── DependencyContainer.swift
766
+ ├── Features/
767
+ │ ├── Login/
768
+ │ │ ├── Views/
769
+ │ │ │ ├── LoginView.swift
770
+ │ │ │ └── LoginFormView.swift
771
+ │ │ ├── ViewModels/
772
+ │ │ │ └── LoginViewModel.swift
773
+ │ │ ├── UseCases/
774
+ │ │ │ └── LoginUseCase.swift
775
+ │ │ └── Models/
776
+ │ │ └── LoginError.swift
777
+ │ ├── UserList/
778
+ │ │ ├── Views/
779
+ │ │ ├── ViewModels/
780
+ │ │ └── Models/
781
+ │ └── ...
782
+ ├── Domain/
783
+ │ ├── Models/
784
+ │ │ └── User.swift
785
+ │ └── Repositories/
786
+ │ ├── UserRepository.swift
787
+ │ └── AuthRepository.swift
788
+ └── Resources/
789
+ ├── Assets.xcassets
790
+ └── Localizable.strings
791
+ ```
792
+
793
+ ### 7.2 MARK Comments
794
+
795
+ **Check for:**
796
+ - [ ] Consistent MARK usage
797
+ - [ ] Logical section ordering
798
+ - [ ] Protocol conformances in extensions
799
+
800
+ **Example:**
801
+ ```swift
802
+ final class UserViewModel {
803
+ // MARK: - Properties
804
+ private let userRepository: UserRepository
805
+ @Published var users: [User] = []
806
+
807
+ // MARK: - Initialization
808
+ init(userRepository: UserRepository) {
809
+ self.userRepository = userRepository
810
+ }
811
+
812
+ // MARK: - Public Methods
813
+ func loadUsers() async {
814
+ // Implementation
815
+ }
816
+
817
+ // MARK: - Private Methods
818
+ private func processUsers(_ users: [User]) -> [User] {
819
+ // Implementation
820
+ }
821
+ }
822
+
823
+ // MARK: - Equatable
824
+ extension UserViewModel: Equatable {
825
+ static func == (lhs: UserViewModel, rhs: UserViewModel) -> Bool {
826
+ lhs.users == rhs.users
827
+ }
828
+ }
829
+ ```
830
+
831
+ ---
832
+
833
+ ## Quick Architecture Checklist
834
+
835
+ ### Critical
836
+ - [ ] Clear separation of concerns (View/ViewModel/Model)
837
+ - [ ] Dependencies injected (no singletons or hard dependencies)
838
+ - [ ] Protocol-based abstractions
839
+ - [ ] Testable architecture
840
+
841
+ ### High Priority
842
+ - [ ] Repository pattern for data access
843
+ - [ ] Use cases for business logic
844
+ - [ ] Coordinator for navigation
845
+ - [ ] Unit tests for ViewModels and use cases
846
+
847
+ ### Medium Priority
848
+ - [ ] Consistent file organization
849
+ - [ ] MARK comments for sections
850
+ - [ ] Environment-based DI in SwiftUI
851
+ - [ ] Integration tests for critical paths
852
+
853
+ ### Low Priority
854
+ - [ ] Dependency container
855
+ - [ ] Feature-based folder structure
856
+ - [ ] UI tests for user flows
857
+
858
+ ---
859
+
860
+ ## Version
861
+ **Last Updated**: 2026-02-10
862
+ **Version**: 1.0.0