为什么需要架构
架构的本质是"为了让人能改得动"。好的架构让代码可测试、可复用、可并行开发,并在需求变更时把改动范围关进最小边界。
架构是权衡,不是信仰。小项目用 MVC 完全够;状态复杂、多人协作时再引入 MVVM/TCA。先理解每种范式的"它解决什么问题",再决定要不要用。
MVC 的真相
苹果版 MVC 里 Controller 是 View 与 Model 的中介。它本不该臃肿——膨胀是因为开发者把本属 Model/Service 的逻辑塞进了 VC。
MVVM 与绑定
MVVM 把状态与逻辑搬进 ViewModel,View(或 VC)只负责把 ViewModel 的数据"绑定"到界面。配合 SwiftUI 的 @Observable 或 Combine,绑定几乎是自动的。
// ViewModel:不依赖任何 UI 框架,纯 Swift 可测
@Observable
final class LoginViewModel {
var username = ""
var password = ""
private(set) var isLoading = false
func login() async {
isLoading = true
defer { isLoading = false }
// 调用 service...
}
}
// View:只描述"状态 -> 界面"
struct LoginView: View {
@State private var vm = LoginViewModel()
var body: some View {
TextField("用户名", text: $vm.username)
SecureField("密码", text: $vm.password)
Button("登录") { Task { await vm.login() } }
.disabled(vm.isLoading)
}
}
保持 ViewModel 与 UIKit/SwiftUI 解耦,它就能脱离界面做单元测试:直接调用方法、断言状态字段,无需启动 App。
TCA 单向数据流
The Composable Architecture 把状态变化收束成"状态 + 动作 + Reducer"的纯函数:所有变更经一个入口,便于测试、回放与推理。
@Reducer
struct Counter {
struct State { var count = 0 }
enum Action { case increment, decrement }
var body: some ReducerOf {
Reduce { state, action in
switch action {
case .increment: state.count += 1; return .none
case .decrement: state.count -= 1; return .none
}
}
}
}
TCA 适合状态交互复杂、需要强可测性的中大型项目。它带来学习成本与样板代码,小项目用 MVVM 更划算。先懂单向数据流的思想,再决定是否引入框架。
依赖注入
不要在被依赖方里直接 new 具体实现,而是把"接口"传进去。这样生产用真实网络、测试用 Mock,互不干扰。
protocol UserRepository {
func currentUser() async throws -> User
}
struct APIUserRepository: UserRepository { /* 真实实现 */ }
final class ProfileViewModel {
private let repo: UserRepository
init(repo: UserRepository) { self.repo = repo } // 注入,而非内部创建
}
// 测试时传入 Mock
let vm = ProfileViewModel(repo: MockUserRepository())
Coordinator 路由
把"页面跳转逻辑"从 ViewController 里抽出来交给 Coordinator,VC 只发"我想去某处"的事件,由 Coordinator 决定如何导航。这样 VC 可独立测试、复用。
protocol LoginCoordinator {
func showForgotPassword()
func didLogin()
}
// ViewController 持有 coordinator 协议,不直接 present 下一个 VC
模块拆分与分层
用 Swift Package 或本地 module 把代码按"特性/层"切分,控制编译耦合与可见性。典型分层:UI 层 → 业务/状态层 → 服务层 → 基础设施层。
| 层 | 职责 | 依赖方向 |
|---|---|---|
| UI 层 | SwiftUI/UIKit 视图与交互 | 依赖下层 |
| 业务层 | ViewModel / Use Case / Reducer | 依赖服务接口 |
| 服务层 | 网络、持久化、定位等实现 | 依赖基础设施 |
| 基础设施 | 底层库、工具、第三方封装 | 不依赖上层 |
上层依赖下层,下层绝不反向引用上层,否则形成循环依赖、无法单独测试。接口(协议)是跨层解耦的关键武器。
常见陷阱与 FAQ
尽量避免。一旦引入 UIKit/SwiftUI,它就绑死在 UI 框架上,无法脱离界面做单元测试。颜色、字体等"展示细节"应在 View 层决定,ViewModel 只持有数据。
用 Combine 或 RxSwift 把 ViewModel 的发布者订阅到 UI 控件;iOS 17+ 也可让 ViewModel 遵循 @Observable,在 VC 里 KVO 式观察。SwiftUI 则靠属性包装器自动绑定,最省心。
原型、单人小项目、状态极其简单的页面。TCA 的样板与概念成本在小规模下是负收益。它的价值随状态复杂度与团队规模上升而凸显。