开发 iOS 应用时,视图控制器很容易逐渐变得臃肿。
绘制页面、处理数据,甚至跳转到下一页面的pushViewController都混在了一起。
如果放任不管,页面流程越复杂,就越难追踪从哪里跳到哪里。
今天介绍能将页面跳转逻辑从视图控制器中清晰分离的iOS Coordinator 模式。
Coordinator 模式会添加一个专门负责页面跳转的独立对象。视图控制器只负责显示页面,下一步去哪里则交给 Coordinator。
什么是 Coordinator 模式?
一句话定义如下。
Coordinator 是专门负责管理页面跳转(导航)流程的对象。
过去,视图控制器要独自完成页面绘制、接收用户输入以及跳转到下一页面。
只需把其中的跳转到下一页面提取出来,交给 Coordinator 处理。
按钮被点击后,视图控制器只要告诉 Coordinator“按钮被点击了”。
它不需要知道具体去哪里,也不需要知道如何跳转。
这样,视图控制器之间就不必直接互相了解。A 页面无需知道 B 页面存在,复用也容易得多。
为什么要分离页面跳转?
把页面跳转代码放在视图控制器中,会带来不少问题。
下面总结一些我亲身遇到的问题。
- 依赖关系纠缠:A 页面直接创建 B 页面,因此 B 发生变化时 A 也要修改。
- 难以复用:想在其他流程中使用同一页面,就必须再次修改跳转代码。
- 流程难以理解:页面跳转逻辑散落在 20 个文件中,无法看到全局。
- 难以测试:跳转逻辑与视图控制器绑定,很难单独测试。
将跳转逻辑集中到一处,就能解决其中大部分问题。
只看一个 Coordinator 文件,就能了解整个应用的页面流程。
如何创建 Coordinator?
基本结构比想象中简单。
首先定义一个作为共同规范的协议。start()充当起点。
protocol Coordinator: AnyObject {
var navigationController: UINavigationController { get }
func start()
}
然后创建实际的 Coordinator,负责创建并显示首个页面。
final class MainCoordinator: Coordinator {
let navigationController: UINavigationController
init(nav: UINavigationController) { self.navigationController = nav }
func start() {
let vc = HomeViewController()
vc.coordinator = self // 接收跳转请求的通道
navigationController.pushViewController(vc, animated: false)
}
func showDetail() { // 下一页面的跳转只在这里处理
let vc = DetailViewController()
navigationController.pushViewController(vc, animated: true)
}
}
按钮被点击后,视图控制器只需调用coordinator?.showDetail()即可。
不用关心要去哪个页面,也不用关心如何执行 push。
它和直接 push、Segue 有什么不同?
这部分很容易混淆,下面用表格整理。
| 项目 | 直接 push / Segue | Coordinator 模式 |
|---|---|---|
| 跳转逻辑位置 | 视图控制器内部 | 集中在 Coordinator 中 |
| 页面间依赖 | 彼此直接了解 | 不必了解彼此 |
| 页面复用 | 困难 | 容易 |
| 理解流程 | 分散在多个文件中 | 集中管理 |
| 初期工作量 | 少 | 稍多 |
如你所见,Coordinator 并不是万能的。
如果只是只有两三个页面的小应用,反而可能增加代码,得不偿失。
但对于登录、引导流程、标签页切换等多分支流程的应用,它确实很有价值。
常见问题
Q. 页面很多时,也要创建多个 Coordinator 吗?
是的。通常按流程拆分,例如登录流程和主流程。上层 Coordinator 通常负责管理子 Coordinator。
Q. 什么时候清理子 Coordinator?
流程结束后,父对象应从数组中移除对子对象的引用。否则它会一直留在内存中并造成泄漏。
Q. SwiftUI 中也使用吗?
SwiftUI 提供了基于NavigationStack和path的路由,因此方式略有不同。不过,将流程提取到独立对象中的思路仍然适用。
刚开始时,多出一个文件可能会让人觉得麻烦。
但当页面增加到十个、二十个时,你就会体会到这种结构的价值。
先尝试将一个小型页面流程提取到 Coordinator 中。你会亲自感受到视图控制器变轻了。🙂

