现状

大部分 iOS 开发者会在 UIViewController 里编写 present/push 的代码展示另一个 UIViewController,也习惯响应关闭按钮的点击事件后调用 dismiss / pop 来关闭自身。

这样有什么问题?

  1. 难以复用:展示方式写死了,不同场景要写 if/else
  2. 灵活性低:页面展示的流程是固定的,难以插入或去掉
  3. 耦合性高:依赖了其他 UIViewController 的类信息(甚至更多)
  4. 职责不合理:UIViewController 知道被放在 navigation 或者 tab 中,也知道自己的展现形式
  5. 维护成本高:负责了这部分职责后,UIViewController 的代码会膨胀

举个简单例子:

截屏2021-06-13 下午9.43.08

CoordinatorDemo-add demo

如何改进

引入一个通用的角色,负责页面转场/流程的组合和调度,这就是 Coordinator(协调者)

Coordiantor 的职责:

上面的例子通过引入 Coordinator 层,整体架构如下:

截屏2021-06-13 下午9.43.34

第一次重构

根据上述思想,可以给每一个 UIViewController 搭配一个对应的 Coordinator,这样重构之后,UIViewController 之间没有显式耦合,需要改动流程和展示方式时不需要修改 UIViewController 中的代码,代码变得干净和职责分明。

CoordinatorDemo-first refactor

截屏2021-06-13 下午9.44.05

第二次重构

第一次重构后,代码职责已经清楚了,基本达到架构演进的目的。接下来可以考虑怎么让代码更优雅。这里面虽然 UIViewController 之间没有显式依赖了,但是由于显式依赖了 Coordinator,其中有其他 UIViewController 的依赖,所以还存在间接依赖。解决这个问题也很简单,引入 Protocol,将 Coordinator 的结构抽象成协议,而非具体的类,这样耦合就被彻底拆开了。在不同宿主底下,可以使用不同的实现。

CoordinatorDemo-second refactor

截屏2021-06-13 下午9.44.30

第三次重构

第二次重构后,回头看看开头的几个问题

  1. 展示方式通过不同的实现解决
  2. 流程通过不同 Coordinator 连接
  3. UIViewController 之间没有耦合
  4. UIViewController 不感知自己被如何展示
  5. UIViewController 中的代码减少了

问题基本得到解决。更进一步思考在 iOS 上,还可以通过什么手段来更使 Coordinator 更强大呢?如果将 Coordinator 继承自 UIViewController,而将原来的 UIViewController 作为 CoordinatorchildController

可以获得以下特性:

  1. Coordinator 不需要由 UIViewController 持有,而是由系统管理
  2. UIViewControllerCoordinator 的依赖变成了 Delegate,可以不依赖 Coordinator 协议
  3. 可以使用 childControlleradd/remove 进行管理
  4. 能承担响应链和UITraitCollectionUIViewController 才有的特性

截屏2021-06-13 下午9.44.53

经过重构后所谓的 Coordinator 就是其实就是原来的 UIViewController,而将其展示的部分拆成了新的 childController 专门用于视图展示和处理(在有些文章中也被称为 FlowController)。

CoordinatorDemo-third refactor

总结

项目中可以根据需要进行演进,第一次重构已经能实现拆分的思想,第二次重构增加了灵活性,第三次重构依赖了 iOS 目前已有的 API,在架构上反而不是一件太好的事情,因为不同的系统能力不一定对齐,甚至在同系统上(比如引入 FlutterSwiftUI 后)也不一定能够长久使用。

参考

漫谈 iOS 架构:从 MVC 到 VIPER,以及 Redux | Nelson

Coordinator and FlowController