iOS应用架构设计:MVC、MVVM、VIPER,我在杭州卷完代码还要卷公考
上周五晚上十点半,我合上 MacBook Pro,盯着屏幕上那堆耦合到天际的 ViewController 代码,脑子里只有一个念头:这玩意要是能重构,我现在就去西湖边跑十圈。
我是谁?一个在杭州某二线互联网公司搬砖的 iOS 程序员,白天写 Swift 搞性能优化,晚上刷行测申论准备考公。没错,就是那种“白天被产品经理催需求,晚上被粉笔APP催打卡”的夹心饼干。阿里网易就在隔壁,机会多得像西湖边的共享单车,但我也清楚——35岁前不上岸,可能就得靠骑单车送外卖了。
说回正题。我们项目是个电商类 App,去年双11期间线上崩了两次,原因?ViewController 超过 2000 行,网络请求、UI 更新、业务逻辑全塞一块儿,改个按钮颜色都能触发支付回调。运维老哥半夜打电话骂我:“你这代码是拿脚写的吧?”我当时真想砸电脑。
痛定思痛,领导拍板:架构必须重构。可选方案有仨:MVC、MVVM、VIPER。别看都是字母缩写,背后可是完全不同的哲学。今天这篇不是教科书式的理论对比,而是我踩坑、加班、被测试追着问进度后的真实复盘。
起点:MVC —— 苹果给的糖,吃多了也齁
刚入行时,Apple 官方文档和《iOS Programming: The Big Nerd Ranch Guide》(这本书我翻烂了)都把 MVC 当成默认答案。Model-View-Controller,听起来很美:Model 管数据,View 管展示,Controller 协调一切。
但现实是,iOS 的 MVC 很容易变成 Massive View Controller。
// 典型的“Massive VC”现场
class ProductDetailViewController: UIViewController {
@IBOutlet weak var priceLabel: UILabel!
@IBOutlet weak var buyButton: UIButton!
private var product: Product?
override func viewDidLoad() {
super.viewDidLoad()
fetchProduct() // 网络请求
setupUI() // UI配置
trackEvent() // 埋点
checkInventory() // 库存逻辑
}
func fetchProduct() {
NetworkManager.shared.request(.product(id: "123")) { [weak self] result in
switch result {
case .success(let data):
self?.product = Product(from: data)
self?.updateUI() // 直接操作UI
case .failure(let error):
self?.showError(error)
}
}
}
}
这段代码是不是眼熟?几乎每个 iOS 项目初期都会这么写。问题在哪?Controller 既要管网络,又要管 UI 更新,还得处理业务规则。测试?别想了,Mock 都不知道从哪下手。
我们在上一个版本就这么干的,结果双11当天用户点击“立即购买”没反应——因为库存检查逻辑和网络回调混在一起,异步顺序出错。测试小妹幽幽地说:“你们程序员是不是觉得用户不会同时点两次按钮?”
进阶:MVVM —— 数据绑定真香,但别信 SwiftUI 的鬼话
被教训后,我翻开了《iOS Architecture Patterns》这本书(技术群里人手一本),决定试试 MVVM(Model-View-ViewModel)。
核心思想很简单:把 Controller 的业务逻辑抽到 ViewModel 里,View 只负责展示,ViewModel 负责提供数据和状态。配合 RxSwift 或 Combine,还能玩响应式编程。
// ViewModel 把脏活累活扛了
class ProductDetailViewModel {
let product = CurrentValueSubject<Product?, Never>(nil)
let isLoading = CurrentValueSubject<Bool, Never>(false)
let errorMessage = CurrentValueSubject<String?, Never>(nil)
func loadProduct(id: String) {
isLoading.send(true)
NetworkManager.shared.request(.product(id: id)) { [weak self] result in
DispatchQueue.main.async {
self?.isLoading.send(false)
switch result {
case .success(let data):
self?.product.send(Product(from: data))
case .failure(let error):
self?.errorMessage.send(error.localizedDescription)
}
}
}
}
}
// ViewController 瘦成一道闪电
class ProductDetailViewController: UIViewController {
@IBOutlet weak var priceLabel: UILabel!
private let viewModel = ProductDetailViewModel()
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
viewModel.loadProduct(id: "123")
}
private func bindViewModel() {
viewModel.product
.compactMap { $0 }
.receive(on: DispatchQueue.main)
.sink { [weak self] product in
self?.priceLabel.text = "¥\(product.price)"
}
.store(in: &cancellables)
}
}
效果立竿见影:ViewController 行数从 1800+ 降到 300 以内,ViewModel 可以独立单元测试,连测试小妹都夸我“这次没埋雷”。
但别高兴太早。MVVM 的坑在于“过度设计”。有些团队一上来就搞 RxSwift + RxCocoa,结果新人看了代码直呼“这是 Swift 还是 Rx 咒语?”而且,SwiftUI 虽然原生支持 MVVM,但实际项目里混合使用 UIKit 和 SwiftUI 时,状态管理容易撕裂。
有一次我用 @Published 和 CurrentValueSubject 混着用,结果状态更新不一致,UI 卡在 loading 状态。查了三天,最后发现是 Combine 的调度队列没切主线程。那一刻,我真的想转行卖煎饼。
终极解?VIPER —— 架构洁癖患者的狂欢
听说阿里某些核心 App 用 VIPER,我心动了。VIPER 是 View-Interactor-Presenter-Entity-Router 的缩写,把职责拆到极致,每个模块只干一件事。
- View: 只管 UI 展示和用户交互
- Presenter: 处理 View 的输入,协调 Interactor
- Interactor: 包含业务逻辑
- Entity: 纯数据模型
- Router: 负责页面跳转
听起来是不是特别“干净”?但代价是:文件数量爆炸。一个简单页面要建 5 个类。
// Interactor - 专注业务
class ProductDetailInteractor {
weak var output: ProductDetailInteractorOutput?
func fetchProduct(id: String) {
NetworkManager.shared.request(.product(id: id)) { result in
switch result {
case .success(let data):
let product = Product(from: data)
self.output?.didFetchProduct(product)
case .failure(let error):
self.output?.didFailWithError(error)
}
}
}
}
// Presenter - 中间人
class ProductDetailPresenter {
weak var view: ProductDetailViewInput?
var interactor: ProductDetailInteractorInput
var router: ProductDetailRouterInput
func viewDidLoad() {
view?.showLoading()
interactor.fetchProduct(id: "123")
}
func didTapBuyButton() {
router.routeToPayment()
}
}
我们拿一个小功能模块试水 VIPER。结果?开发效率暴跌。每天光在五个文件之间跳转就能把我累死。更糟的是,App Store 审核时因为 Router 跳转逻辑复杂,被误判为“隐藏功能”,差点被拒。
但 VIPER 也不是一无是处。对于高度复杂的业务(比如金融交易、订单流程),它的清晰边界确实能降低长期维护成本。只是对我们这种电商 App,有点杀鸡用牛刀。
三架构横向对比:别被名词忽悠了
| 维度 | MVC | MVVM | VIPER |
|---|---|---|---|
| 上手难度 | ⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 代码量 | 少(但臃肿) | 中等 | 极多 |
| 可测试性 | 差 | 好 | 极好 |
| 团队协作 | 容易冲突 | 较清晰 | 需严格规范 |
| 适合场景 | 小型/原型项目 | 中大型项目 | 超大型/高稳定性要求 |
我的结论很务实:没有银弹,只有权衡。
- 新项目起步?MVC 快速验证 MVP。
- 项目成长到中等规模?MVVM 是甜区,尤其配合 Combine(毕竟 Apple 自家亲儿子)。
- 核心交易链路?可以局部用 VIPER,但别全盘照搬。
考公程序员的终极心得:架构是手段,不是目的
现在我们的 App 用的是 MVVM + 轻量级 Coordinator 模式(Router 的简化版),既保证了可维护性,又不至于让新人第一天就提离职。
上周上线新版本,零崩溃。测试小妹居然请我喝了杯瑞幸——虽然她说“下次别再把埋点逻辑写在 ViewModel 里了”。
回头想想,学架构不是为了炫技,而是为了睡个安稳觉。毕竟,我晚上还得刷 50 道言语理解题呢。考公路上,代码整洁一点,bug 少一点,就能多留半小时给申论素材积累。
最后送大家一句我在《iOS组件化架构》书里看到的话:“好的架构,是让后来者能轻松接手,而不是让面试官眼前一亮。”
共勉。毕竟在杭州,我们不仅要卷代码,还得卷上岸。

评论 0