← 返回文章列表
记饺 App 的 SwiftUI 架构演进
记饺是个个人记账 App,最早用 MVVM 快速上线,后来数据复杂度和交互状态增加,改成了 TCA(The Composable Architecture)。这篇记录一下两个阶段的对比和迁移心得。
第一阶段:MVVM
class LedgerViewModel: ObservableObject {
@Published var records: [Record] = []
@Published var monthlyTotal: Double = 0
func addRecord(_ r: Record) {
records.append(r)
recalculate()
}
}
两个 ViewModel 互相依赖时就开始乱:分类页和记账页共用一个 CategoryViewModel,但各自需要的排序逻辑不同。@Published 的变化触发链也变得不可预测。
第二阶段:TCA
struct LedgerReducer: Reducer {
struct State: Equatable {
var records: [Record] = []
var monthlyTotal: Double = 0
var isLoading = false
}
enum Action: Equatable {
case addRecord(Record)
case deleteRecord(UUID)
case loadFromCloud
case cloudResponse(TaskResult<[Record]>)
}
func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .addRecord(let r):
state.records.append(r)
return .none
case .loadFromCloud:
state.isLoading = true
return .run { send in
await send(.cloudResponse(TaskResult { try await cloud.fetch() }))
}
case .cloudResponse(.success(let r)):
state.records = r
state.isLoading = false
return .none
case .cloudResponse(.failure):
state.isLoading = false
return .none
}
}
}
对比
| 维度 | MVVM | TCA |
|---|---|---|
| 状态管理 | 分散在多个 ViewModel | 单一 State 树 |
| 可测试性 | 需要 mock ViewModel | 纯函数测试 reducer |
| 副作用 | 散落各处 | 集中到 Effect |
| 调试 | 靠 print | Reducer 可 replay |
| 学习曲线 | 低 | 陡(Pointfree 的库范式) |
数据持久化
Core Data + iCloud 同步。大坑是首次同步的合并冲突——两个设备离线各记了几笔,上线后 NSPersistentCloudKitContainer 的默认策略无法合并自定义冲突。最终重写了 resolveConflicts,按时间戳 last-write-wins。
结论:个人项目用 MVVM 够用。一旦涉及复杂状态流和多人协作,TCA 的收益才明显。