iOS 工程

[iOS架构 #5] iOS VIPER架构:大型应用为何采用后又离开(包括RIBs)

在iOS架构中,很少有哪种模式像VIPER一样引发如此两极分化的评价。

4 分钟阅读
[iOS架构 #5] iOS VIPER架构:大型应用为何采用后又离开(包括RIBs) 封面图

在iOS架构中,很少有哪种模式像VIPER一样引发如此两极分化的评价。

有人称它是“大型团队协作的答案”,也有人称它是“样板代码地狱”。实际上,不少大型服务应用曾在一段时期采用VIPER或其变体,几年后又迁移到更轻量的结构。

今天我们来总结VIPER试图解决什么问题,以及为什么代价如此高昂。


VIPER由五个部分组成

VIPER将一个页面拆分为五种职责,名称本身就是这五个职责的首字母。

  • View:负责显示页面,包括视图控制器,只“负责绘制”。
  • Interactor:业务逻辑。获取数据并应用规则。
  • Presenter:View与Interactor之间的中介,将数据加工为页面所需的形式。
  • Entity:纯数据模型。
  • Router(Wireframe):负责页面跳转。

它把MVC(Model-View-Controller)中一个视图控制器承担的工作拆成四部分,并将页面跳转单独抽成Router。也就是说,它为系列第1篇介绍的Massive View Controller的每项职责都安排了专属位置。

核心规则是近似单向的严格引用关系。View只了解Presenter,Presenter了解Interactor和Router,Interactor只接触Entity。每个边界都通过协议切开,因此任何部分都可以替换为mock。


有哪些好处?

这种结构确实有发挥作用的时刻。

第一,可以将测试覆盖率提升到极限。所有边界都是协议,因此Interactor、Presenter和Router都能分别进行单元测试。

第二,大型团队的分工更容易。每个页面的结构都相同,因此具有可预测性:“不管是谁编写的页面,文件结构都一样”。几十人共同维护一个代码库时,这种统一性确实很有价值。

第三,它很适合按功能模块化。由于一个页面是自包含的五个部分,因此很容易按功能拆出模块。Uber受VIPER启发创建的RIBs,就是将这一方向推向极致的案例:不是按View,而是按业务逻辑单元将应用拆分成树状结构。

即使只有一个按钮的页面,也会被强制拆成同样的五个部分
即使只有一个按钮的页面,也会被强制拆成同样的五个部分

为什么大家都离开了?

问题在于成本。

样板代码多得惊人。即使是只有一个按钮的页面,View、Interactor、Presenter、Entity、Router,加上它们之间的协议,也会产生六七个文件。“改一个标签文本要经过三个文件”并不是无病呻吟。

即使是简单页面,也会被强制承受同样的重量。静态设置页面和复杂的信息流页面都同样包含五个部分。结构的重量与页面复杂度并不成比例。

学习曲线和上手成本也不低。第一次看到数据沿着View → Presenter → Interactor → Presenter → View往返的人,需要花时间才能跟上这条路径。

而最后一击是SwiftUI的出现。VIPER源于UIKit时代“将职责从视图控制器中移出”的问题意识。但在SwiftUI中,View本身已经很轻量,页面跳转方式也不同,因此Router之类的部分显得不自然。问题本身已经变了。


所以,VIPER是失败的模式吗?

很难这样说。VIPER留下的遗产已经融入如今的标准实践。

  • 将页面跳转交给专用对象 → 发展为Coordinator模式
  • 将业务逻辑与表示层分离 → 发展为UseCase/Repository层
  • 用协议切分边界 → 发展为依赖注入和测试实践

虽然完整使用VIPER五个部分的团队变少了,但那五个部分试图解决的问题及其解决方案都保留了下来。如今更接近于只挑选所需部分来使用。

整体退出了,但有用的部分作为标准实践保留下来
整体退出了,但有用的部分作为标准实践保留下来

总结

  • VIPER将一个页面拆分为View、Interactor、Presenter、Entity和Router五个部分,并通过协议切分边界。
  • 易于测试以及大型团队中的统一性是它的优点,但每个页面需要六七个文件,样板代码成本很高。
  • 随着SwiftUI时代的问题意识发生变化,采用原始VIPER的团队减少了。
  • 不过,它的遗产仍作为标准实践存在,例如Router→Coordinator、Interactor→UseCase。

下一篇将介绍这一遗产中最普及的形式:Clean Architecture的UseCase和Repository在iOS中实际负责什么。

延伸阅读