iOS 工程

[iOS 架构 #10] Uber RIBs:按业务逻辑拆分应用

RIBs 按业务逻辑单元而不是屏幕拆分应用。本文整理无屏幕组件存在这一前提、它只适合超大型组织的原因,以及它与 VIPER 的区别。

4 分钟阅读
[iOS 架构 #10] Uber RIBs:按业务逻辑拆分应用 封面图

在 VIPER(View·Interactor·Presenter·Entity·Router)篇中,我曾用一段话带过“Uber 受 VIPER 启发创建了 RIBs”,这次将详细介绍。在韩国,RIBs 也因 Toss 采用了这种架构而广为人知。

理解 RIBs 的关键只有一个。此前介绍的所有架构(MVC·MVVM·VIPER·ReactorKit)都以屏幕为基本单元。RIBs 放弃了这一前提,改为按业务逻辑单元拆分应用,而不是按屏幕拆分。


无屏幕组件的存在

RIBs 是 Router·Interactor·Builder 的缩写。这三者是每个组件(RIB)的必需部分,只有需要时才会加入 Presenter 和 View。

  • Interactor:业务逻辑的核心,也是 RIB 的大脑。
  • Router:通过 attach 和 detach 子 RIB 来管理树。
  • Builder:组装一个 RIB,并注入依赖。
  • Presenter·View:可选项,只存在于需要屏幕的 RIB 中。

“View 是可选项”这一点至关重要。以叫车应用的“乘车中”状态为例,它涵盖地图屏幕、司机信息卡和支付准备逻辑,但本身并不是某一个具体屏幕。在 RIBs 中,可以将其做成无屏幕的 viewless RIB,再在其下连接拥有屏幕的子 RIB。

整个应用会变成一棵 RIB 树。登录状态、乘车状态等应用状态的变化,就是树形结构的变化。退出登录时,LoggedIn RIB 会从树中整体移除,其下的屏幕和逻辑也会一起消失。状态管理就是树管理。

展示 RIBs 树形结构,以及无屏幕 RIB 与拥有 View 的 RIB 区别的示例图
登录、乘车等应用状态决定树的形状

为什么只有超大型组织会使用?

RIBs 从一开始的设计目标就是“数百人共同开发一个应用”。Uber 应用中有数十个团队同时提交代码,仅按屏幕拆分无法建立团队边界,因为一个屏幕中会混入多个团队的逻辑。

按 RIB 拆分后,每个团队都能拥有自己的 RIB 子树。Builder 强制规定依赖注入点,因此根本无法接触其他团队 RIB 的内部。这是在架构层面实现模块化系列中提到的“强制边界”。

Toss 采用 RIBs 的原因也相同。在一个应用包含数十种金融服务的超级应用结构中,让每项服务成为一棵 RIB 子树后,连接和移除都会更加明确。

反过来,这些优点在小型团队中都会变成成本。

第一,RIBs 的样板代码比 VIPER 还多。一个 RIB 通常会产生四五个文件。因此 Uber 也提供了代码生成模板。

第二,学习曲线很陡。团队必须掌握树设计、attach/detach 时机以及按作用域进行依赖注入,才能形成统一的理解。

第三,它与以屏幕为中心的思维方式不一致。产品需求通常按屏幕编写,而 RIBs 按状态拆分,因此设计阶段需要进行转换。

它与 VIPER 有什么不同?

只看组件名称,RIBs 像是 VIPER 的变体,但两者的差异是根本性的。

VIPER RIBs
基本单元 屏幕 业务逻辑
无屏幕组件 不可能 viewless RIB
导航 Router 负责屏幕切换 Router 负责树的 attach/detach
目标 分离一个屏幕的职责 按组织单元分离代码所有权

VIPER 的 Router 负责“如何进入下一个屏幕”,而 RIBs 的 Router 负责“在当前应用状态下,哪些逻辑组件应该存活”。名称相同,问题不同。

展示 RIBs 团队代码所有权,以及 TEAM A、B、C 子树拆分的插图
让每个团队拥有自己的子树,才是 RIBs 的真正目标

总结

  • RIBs 是 Uber 的架构,用业务逻辑单元而不是屏幕将应用拆成树形结构。
  • RIB 可以没有屏幕,并通过树的 attach/detach 表示应用状态变化。
  • 它的目标是按团队划分代码所有权并强制边界,因此在数百人规模的组织中最能发挥价值。Uber 和 Toss 是代表案例。
  • 对于小型团队,样板代码和学习曲线会压过这些优点。正如第 8 篇选择指南所述,团队规模是大部分答案。

至此,iOS 架构系列中只提到但未展开的部分也全部补齐了。从 MVC 到 RIBs,请记住所有架构最终都是对同一个问题“逻辑应该放在哪里”的不同回答。

延伸阅读

iOS 架构系列