iOS 工程

iOS Coordinator 模式:将页面跳转代码从视图控制器中分离出来

开发 iOS 应用时,视图控制器很容易逐渐变得臃肿。

4 分钟阅读
iOS Coordinator 模式:将页面跳转代码从视图控制器中分离出来 封面图

开发 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()即可。

点击一次按钮,剩下的交给 Coordinator 处理。
点击一次按钮,剩下的交给 Coordinator 处理。

不用关心要去哪个页面,也不用关心如何执行 push。

只留下这一行调用,视图控制器就轻松多了。
只留下这一行调用,视图控制器就轻松多了。

它和直接 push、Segue 有什么不同?

这部分很容易混淆,下面用表格整理。

项目 直接 push / Segue Coordinator 模式
跳转逻辑位置 视图控制器内部 集中在 Coordinator 中
页面间依赖 彼此直接了解 不必了解彼此
页面复用 困难 容易
理解流程 分散在多个文件中 集中管理
初期工作量 少 稍多

如你所见,Coordinator 并不是万能的。

如果只是只有两三个页面的小应用,反而可能增加代码,得不偿失。

但对于登录、引导流程、标签页切换等多分支流程的应用,它确实很有价值。

流程分支越多的应用,越能体现它的价值。
流程分支越多的应用,越能体现它的价值。

常见问题

Q. 页面很多时,也要创建多个 Coordinator 吗?

是的。通常按流程拆分,例如登录流程和主流程。上层 Coordinator 通常负责管理子 Coordinator。

Q. 什么时候清理子 Coordinator?

流程结束后,父对象应从数组中移除对子对象的引用。否则它会一直留在内存中并造成泄漏。

Q. SwiftUI 中也使用吗?

SwiftUI 提供了基于NavigationStack和path的路由,因此方式略有不同。不过,将流程提取到独立对象中的思路仍然适用。


刚开始时,多出一个文件可能会让人觉得麻烦。

但当页面增加到十个、二十个时,你就会体会到这种结构的价值。

先尝试将一个小型页面流程提取到 Coordinator 中。你会亲自感受到视图控制器变轻了。🙂

延伸阅读