做了两年iOS开发,我终于搞懂MVC、MVVM和VIPER该怎么选
去年国庆前,我们组突然接到一个新项目:给集团内部员工做一款移动端审批工具。听起来平平无奇对吧?但需求文档里赫然写着“体验要对标钉钉”、“性能要秒开”、“上线时间两周后”。我当时正啃着食堂的红烧排骨,差点没噎住——这不就是典型的“既要马儿跑,又要马儿不吃草”吗?
我是那种在国企混了快两年的程序员,日常双休不加班,用Mac写代码,Windows只用来测兼容性(其实也就测个微信分享)。前端这块我向来感兴趣,尤其是动效和交互动画,业余还捣鼓过几个SwiftUI小玩具。但一听到这种“对标大厂”的需求,心里还是咯噔一下:架构没选好,后面全是坑。
为什么架构这事不能糊弄?
说实话,刚入职那会儿我也觉得架构是“高大上”的东西,真正干活还不是if-else堆起来?直到有一次线上事故:用户点击审批按钮后App直接闪退,查了半天才发现是因为ViewController里塞了太多业务逻辑,状态管理乱成一锅粥。那次被运维拉去开会,产品经理还阴阳怪气地说:“你们技术是不是又在炫技?”
从那以后我悟了:架构不是炫技,是防坑。
这次新项目,我决定认真对待架构选型。翻了翻Apple官方文档,又扒了几个开源项目的源码,最后锁定了三个主流方案:MVC、MVVM、VIPER。下面说说我的实战踩坑经历。
MVC:祖传架构,能跑就行?
MVC(Model-View-Controller)是Apple亲儿子,Xcode新建项目默认就给你搭好了架子。以前我写demo基本都用它,简单粗暴。
// 典型的MVC ViewController
class ApprovalViewController: UIViewController {
@IBOutlet weak var statusLabel: UILabel!
func loadApprovalData() {
// 网络请求
NetworkService.shared.fetchApproval { [weak self] result in
switch result {
case .success(let data):
self?.updateUI(with: data)
// 还可能在这里处理缓存、埋点、错误重试...
case .failure(let error):
self?.showError(error)
}
}
}
}
看起来没问题?但一旦逻辑复杂起来,ViewController就会膨胀成“Massive View Controller”——动辄上千行代码,测试?不存在的。上次那个闪退Bug就是这么来的。
不过话说回来,在我们这种国企项目里,MVC有时候反而是最优解。为什么?因为人少、需求稳、迭代慢。上周五我跟组长聊,他说:“你别整那些花里胡哨的,能按时上线就行。” 得,MVC保命。
MVVM:数据驱动真香,但别被绕晕
被领导“委婉批评”后,我开始研究MVVM(Model-View-ViewModel)。核心思想是把ViewController里的业务逻辑抽到ViewModel里,通过数据绑定自动更新UI。
用Combine或者RxSwift的话,代码会清爽很多:
class ApprovalViewModel: ObservableObject {
@Published var approvalStatus: String = "待处理"
@Published var isLoading = false
func fetchApproval() {
isLoading = true
NetworkService.shared.fetchApproval()
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { [weak self] completion in
self?.isLoading = false
// 处理错误
}, receiveValue: { [weak self] data in
self?.approvalStatus = data.status
})
.store(in: &cancellables)
}
}
ViewController变得超轻量:
struct ApprovalView: View {
@StateObject private var viewModel = ApprovalViewModel()
var body: some View {
VStack {
Text(viewModel.approvalStatus)
ProgressView().opacity(viewModel.isLoading ? 1 : 0)
}
.onAppear {
viewModel.fetchApproval()
}
}
}
优点很明显:
- 逻辑分离,测试友好(ViewModel纯Swift类,不用依赖UIKit)
- SwiftUI天然契合数据驱动模式
- 团队新人上手快,毕竟现在网上MVVM教程一抓一大把
但也有坑。比如数据流方向容易混乱,特别是多个ViewModel互相依赖时。有次我为了复用逻辑,搞了个“ViewModel工厂”,结果调试的时候自己都绕晕了——这玩意儿到底谁在更新谁?
而且要注意:别为了用Combine而用Combine。有些简单场景,直接回调更直观。我们组实习生就曾写了个20行的Publisher链,其实一个completion block就能搞定……
VIPER:架构洁癖者的终极武器?
VIPER(View, Interactor, Presenter, Entity, Router)号称“Clean Architecture”,每个模块职责单一,测试覆盖率能冲到90%+。
理论上很美:
- View只负责展示
- Presenter处理展示逻辑
- Interactor专注业务规则
- Router管导航
- Entity就是数据模型
但实际用起来……怎么说呢,仪式感太强。新建一个页面要建5个文件,光命名就能纠结半小时。有次我改个按钮文案,得在View→Presenter→Interactor来回跳,差点患上鼠标手。
更致命的是团队成本。我们组除了我,其他同事都是Android转iOS的,让他们理解“为什么不能在Presenter里直接调网络”简直要命。上周Code Review时,老张直接吐槽:“你这代码像洋葱,剥一层还有一层,我头都大了。”
不过VIPER在特定场景确实无敌。比如我们有个复杂的表单页面,涉及10+字段校验、动态显示/隐藏、多步骤提交——用VIPER拆分后,每个模块逻辑清晰,连测试同学都夸“这次Bug少多了”。
实战对比:一张表说清楚
为了说服组长,我做了个横向对比(基于我们项目的实际情况):
| 维度 | MVC | MVVM | VIPER |
|---|---|---|---|
| 上手难度 | ⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 代码量 | 少 | 中 | 多(5倍起) |
| 测试友好度 | 差 | 好 | 极好 |
| 团队接受度 | 高(默认选项) | 中(需培训) | 低(抱怨多) |
| 适合场景 | 简单页面、快速原型 | 中等复杂度、数据驱动型 | 核心流程、高可靠性要求 |
| SwiftUI适配 | 一般(需桥接) | 优秀(@StateObject等原生支持) | 较差(需额外封装) |
最终选择:混合架构才是王道
折腾了一周后,我向组长提了个“非主流”方案:分层混合架构。
- 简单列表页、设置页 → 用MVC(省事)
- 审批详情、表单页 → 用MVVM(数据驱动优势明显)
- 核心审批流程 → 局部引入VIPER思想(比如拆分Interactor处理业务规则)
组长居然同意了!理由很现实:“反正没人查架构规范,能跑就行。”
上线前夜,我紧张地盯着Crashlytics面板,生怕又出幺蛾子。结果?零崩溃!连测试组长都发微信夸:“这次稳定性可以啊。”
给前端同行的几点建议
虽然标题写了“iOS应用架构”,但很多思路对前端也通用(毕竟我平时也玩React/Vue)。分享几个血泪教训:
- 别盲目追新:MVVM火不代表适合你。先看团队技术水平和项目周期。
- SwiftUI是未来:Apple明显在推声明式UI,新项目尽量用SwiftUI+MVVM组合。
- 测试不是可选项:哪怕只写ViewModel的单元测试,也能避免80%的低级错误。
- 文档比代码重要:架构再好,没人懂等于零。我专门写了份《本项目架构使用指南》,连产品经理都能看懂。
最后说个真实故事:App Store审核时,因为用了私有API被拒了两次(其实是某个第三方SDK偷偷调的)。后来我花了三天剥离SDK,顺便重构了网络层——这反而成了项目最稳定的模块。所以说,有时候被迫重构反而是好事。
现在这款审批工具已经稳定运行半年了,日活500+(别笑,国企就这规模)。上周团建吃饭,实习生问我:“哥,下次项目用什么架构?” 我嘬了口冰啤酒,笑着说:“看情况呗,反正周末不加班,有的是时间慢慢调。”
如果你也在纠结架构选型,记住:没有银弹,只有合适。毕竟咱们写代码,是为了下班能准时走人,不是为了造火箭(除非你在SpaceX)。
P.S. 文中所有代码都经过简化,真实项目肯定更复杂。想看完整实现?算了,公司代码不能开源,但我可以推荐几个优质开源项目当教程参考——留言区见!

评论 0