Architecture Patterns
Overview
Quick reference for choosing and implementing iOS architecture patterns. Focused on decision criteria, not tutorials.
Pattern Selection
| Complexity | Pattern | Use When | |------------|---------|----------| | Simple | MV | Single screen, local state only | | Medium | MVVM | 2-5 screens, business logic, network calls | | Complex | TCA | State machines, side effects, complex flows | | Enterprise | Clean | Multiple teams, maximum modularity |
Decision Tree
1. Single screen + local state only?
→ MV (SwiftUI View + @State)
2. Business logic or shared state?
→ MVVM
3. Complex state transitions?
→ TCA
4. Multi-team, high modularity?
→ Clean Architecture
MVVM Pattern (Most Common)
Structure:
- ViewModels:
@Observable, protocol-based DI - Views:
@State private var viewModel - Services: Protocols for testability
Critical Rules:
- ✅ Protocol-based DI via init (enables mocking)
- ✅
@MainActorfor state updates - ✅ Keep Views dumb (delegate to ViewModel)
- ❌ Never import SwiftUI in ViewModels
- ❌ Never use
@Published(use@Observable) - ❌ Never make ViewModels optional
Minimal Example:
protocol AuthServiceProtocol {
func login(_ email: String, _ password: String) async throws -> User
}
@Observable
final class LoginViewModel {
private let authService: AuthServiceProtocol
var email = ""
var password = ""
var isLoading = false
init(authService: AuthServiceProtocol = AuthService()) {
self.authService = authService
}
@MainActor
func login() async {
isLoading = true
defer { isLoading = false }
try? await authService.login(email, password)
}
}
struct LoginView: View {
@State private var viewModel = LoginViewModel()
var body: some View {
Form {
TextField("Email", text: $viewModel.email)
SecureField("Password", text: $viewModel.password)
Button("Login") { Task { await viewModel.login() } }
.disabled(viewModel.isLoading)
}
}
}
TCA Pattern
Use for: Complex state machines, side effects, time-travel debugging
Key: Single Reducer with State/Action/Dependencies. Exhaustive testing via TestStore.
Reference: TCA documentation
Clean Architecture
Use for: Enterprise apps, multiple teams, maximum testability
Patterns:
- VIP (View-Interactor-Presenter): Use
vip-clean-architectureskill for unidirectional data flow, protocol-based boundaries, and Spy-pattern testing - Generic Clean: Domain (entities, use cases) → Data (repositories, network) → Presentation (ViewModels, Views)
Key: Dependency inversion, protocol-based boundaries between layers
References
For detailed implementation examples and migration guides, see:
references/mvvm-patterns.mdreferences/tca-guide.mdreferences/clean-architecture.md
Word count: ~300 (was 2,863) For: Senior/mid iOS engineers who know how to code Focus: Decision-making, not hand-holding