现状
大部分 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
后)也不一定能够长久使用。