现状
大部分 iOS 开发者会在 UIViewController 里编写 present/push 的代码展示另一个 UIViewController,也习惯响应关闭按钮的点击事件后调用 dismiss / pop 来关闭自身。
这样有什么问题?
- 难以复用:展示方式写死了,不同场景要写
if/else - 灵活性低:页面展示的流程是固定的,难以插入或去掉
- 耦合性高:依赖了其他
UIViewController的类信息(甚至更多) - 职责不合理:
UIViewController知道被放在 navigation 或者 tab 中,也知道自己的展现形式 - 维护成本高:负责了这部分职责后,
UIViewController的代码会膨胀
举个简单例子:

如何改进
引入一个通用的角色,负责页面转场/流程的组合和调度,这就是 Coordinator(协调者)。
Coordiantor 的职责:
- 新建和切换画面
- 组合多个子页面为复杂界面
- 实现页面间的回调接收/代理,转换为其他页面需要的数据
上面的例子通过引入 Coordinator 层,整体架构如下:

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

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

第三次重构
第二次重构后,回头看看开头的几个问题
- 展示方式通过不同的实现解决
- 流程通过不同
Coordinator连接 UIViewController之间没有耦合UIViewController不感知自己被如何展示UIViewController中的代码减少了
问题基本得到解决。更进一步思考在 iOS 上,还可以通过什么手段来更使 Coordinator 更强大呢?如果将 Coordinator 继承自 UIViewController,而将原来的 UIViewController 作为 Coordinator 的 childController。
可以获得以下特性:
Coordinator不需要由UIViewController持有,而是由系统管理UIViewController对Coordinator的依赖变成了 Delegate,可以不依赖Coordinator协议- 可以使用
childController的add/remove进行管理 - 能承担响应链和
UITraitCollection等UIViewController才有的特性

经过重构后所谓的 Coordinator 就是其实就是原来的 UIViewController,而将其展示的部分拆成了新的 childController 专门用于视图展示和处理(在有些文章中也被称为 FlowController)。
CoordinatorDemo-third refactor
总结
项目中可以根据需要进行演进,第一次重构已经能实现拆分的思想,第二次重构增加了灵活性,第三次重构依赖了 iOS 目前已有的 API,在架构上反而不是一件太好的事情,因为不同的系统能力不一定对齐,甚至在同系统上(比如引入 Flutter、SwiftUI 后)也不一定能够长久使用。