一、原则:逐步替换、屏蔽风险
- 不要一次性改所有 ViewController → 小 Feature 或屏幕级别切
- 保持 MVC 原有功能可运行 → MVVM / TCA 先是局部替代
- State/Action/Binding 只替换单一 Feature → 避免跨模块依赖链断裂
- 数据流与副作用隔离 → MVVM/TCA 的逻辑尽量不破坏现有 Model/Service
二、切入策略(一步步操作)
1️⃣ 选择低风险、独立 Feature
-
选择依赖少、逻辑简单、UI 独立的模块
-
例子:
- 单页表单
- 列表 + 搜索
- 单独的 Profile 页面
-
避免:AppDelegate/TabBar/复杂导航页作为首切
2️⃣ 从 ViewModel 包裹 Model 开始(MVVM)
-
保持原有 MVC 运行:
- VC 仍然存在,但可以把数据逻辑迁移到 ViewModel
-
操作方式:
// Step 1: 只包裹 Model
class UserViewModel {
private let user: User
var displayName: String { user.name.uppercased() }
}
// Step 2: VC 依赖 ViewModel 而非 Model
class UserViewController: UIViewController {
var viewModel: UserViewModel!
@IBOutlet weak var nameLabel: UILabel!
func refreshUI() {
nameLabel.text = viewModel.displayName
}
}
✅ 结果:
- VC 没有业务逻辑
- 不破坏现有导航和生命周期
- 完全可以在老项目中插入
3️⃣ 逐步引入 TCA / 单向数据流
- 从单独 Feature Store 开始,不要改全局 App State
- State/Action/Reducer 只针对这个 Feature
- Effect 可以先调用现有 Service / API,不用重写全部业务逻辑
示意:
struct CounterState { var count: Int = 0 }
enum CounterAction { case increment, decrement }
let counterReducer = Reducer<CounterState, CounterAction, Void> { state, action, _ in
switch action {
case .increment: state.count += 1
case .decrement: state.count -= 1
}
return .none
}
struct CounterView: View {
let store: Store<CounterState, CounterAction>
var body: some View { /* 用 ViewStore 绑定 */ }
}
重点:不破坏原有 MVC,只在新 View/Feature 使用 TCA
4️⃣ 保持桥接(兼容老代码)
-
ViewController 可以作为 TCA 的 Host:
- 老 VC → 包裹 TCA View
- 新 Feature → 直接 SwiftUI + TCA
-
数据交互用 协议或闭包桥接:
- 避免老 MVC 直接操作新 Store
- 保持单向数据流
5️⃣ 迭代替换
- 新功能 → 全 MVVM / TCA
- 老 VC 的局部逻辑迁移 → 先 ViewModel,再逐步引 TCA
- 随 Feature 更新 → State / Action / Effect 扩展
- 最终可把全局 AppState 逐步整合,老 MVC 可以安全下线
三、实战要点
-
不要试图一次改全局 AppState / 全局 TCA Store
-
ViewModel 优先,再升级为 TCA
-
保留桥接接口:
- 协议 / Delegate / closure → 保持 MVC 与 MVVM/TCA 交互
-
单 Feature 迭代:
- 列表页、表单页 → 先改
- TabBar / Root VC → 最后再考虑
-
测试优先:
- MVVM / TCA 逻辑可单元测试
- 老 MVC 依然可以运行,保证迭代安全
四、一句话总结切入策略
先从独立 Feature 的 ViewModel 开始,把业务逻辑从 VC 拆出,逐步引入 TCA 单向数据流,通过协议/桥接保持 MVC 兼容,最后再迭代替换全局状态。