Coordinator:关键概念,iOS导航的协调器模式

作者: IT Sectr 发布日期: 2026-02-18 阅读时间: 9 分钟

Coordinator(协调器)——一种架构性导航模式,将屏幕间切换的逻辑从ViewController转移到独立的类中。该模式由Soroush Khanlou于2015年提出,并在iOS社区中广泛传播。Coordinator管理应用程序的流程:创建并显示ViewController,在屏幕之间传输数据,并处理流程的完成。该模式通过将导航从控制器中分离来解决Massive View Controller问题。更多详情——请参阅Coordinator原创文章

要点

  • Coordinator——将切换逻辑从ViewController中分离出来的导航模式
  • 职责分离——ViewController管理UI,Coordinator管理导航
  • Router——Coordinator的辅助组件,用于抽象UINavigationController
  • Flow——由一个Coordinator管理的一系列屏幕(例如,引导流程)
  • 委托——子协调器通过delegate/protocol向父级报告

什么是Coordinator:导航模式的本质

Coordinator——一种在iOS应用程序中接管导航责任的模式。在标准的UIKit中,ViewController自行管理切换:present、push、show segue——所有导航方法都从UIViewController调用。Coordinator将此逻辑分离出来:ViewController报告事件(例如,“用户点击了登录按钮”),Coordinator决定接下来显示哪个屏幕。ViewController只保留UI逻辑,并将导航委托给协调器。

模式结构——带有start()和finish()方法的CoordinatorProtocol。start()——流程开始:创建第一个ViewController并显示。finish()——流程完成并通知父协调器。Router——UINavigationController(或UISplitViewController)的包装器,提供show、push、pop、dismiss方法。协调器不直接与UINavigationController交互——仅通过Router。这使得可以测试导航并切换UI框架。

组件角色示例
Coordinator管理导航流程AuthCoordinator, ProfileCoordinator
RouterUINavigationController的抽象push, present, pop, dismiss
ViewControllerUI + 将事件委托给CoordinatorLoginViewController.delegate

Coordinator解决的问题——Massive View Controller(导航——控制器膨胀的常见原因)。在标准的UIKit中,ViewController包含prepareForSegue、导航委托、unwind segues处理。Coordinator消除了这些问题。Storyboard中的Segue——屏幕之间的静态连接,Coordinator提供带条件的动态导航。导航测试成为可能:可以在没有UI的情况下测试Coordinator,通过检查Router调用的顺序。

Swift中的Coordinator:使用Router和Flow的实现

Swift中的基础Coordinator——一个带有Router关联类型和start/finish方法的协议。Router——抽象UINavigationController的协议。Router的具体实现包装UINavigationController并将方法委托给它。Coordinator在init中接收Router并将其用于导航。子协调器存储在childCoordinators数组中,用于生命周期管理。

swift
// Router — 导航抽象
protocol RouterProtocol: AnyObject {
    func push(_ viewController: UIViewController, animated: Bool)
    func pop(animated: Bool)
    func present(_ viewController: UIViewController, animated: Bool)
    func dismiss(animated: Bool)
}

final class NavigationRouter: RouterProtocol {
    private let navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func push(_ vc: UIViewController, animated: Bool) {
        navigationController.pushViewController(vc, animated: animated)
    }

    func pop(animated: Bool) {
        navigationController.popViewController(animated: animated)
    }

    func present(_ vc: UIViewController, animated: Bool) {
        navigationController.present(vc, animated: animated)
    }

    func dismiss(animated: Bool) {
        navigationController.dismiss(animated: animated)
    }
}

// Coordinator — 流程管理
protocol CoordinatorProtocol: AnyObject {
    var childCoordinators: [CoordinatorProtocol] { get set }
    var router: RouterProtocol { get }
    func start()
    func finish()
}

class AuthCoordinator: CoordinatorProtocol {
    var childCoordinators: [CoordinatorProtocol] = []
    let router: RouterProtocol

    init(router: RouterProtocol) {
        self.router = router
    }

    func start() {
        let loginVC = LoginViewController()
        loginVC.onLogin = { [weak self] in
            self?.showHome()
        }
        router.push(loginVC, animated: true)
    }

    private func showHome() {
        let homeCoordinator = HomeCoordinator(router: router)
        childCoordinators.append(homeCoordinator)
        homeCoordinator.start()
    }

    func finish() {
        childCoordinators.removeAll()
        router.pop(animated: true)
    }
}

在AppDelegate/SceneDelegate中创建Coordinator——AppDelegate或SceneDelegate创建UINavigationController,将其包装到NavigationRouter中,创建根Coordinator(AppCoordinator)并调用start()。AppCoordinator根据应用程序状态决定显示引导、登录还是主屏幕。Coordinator——导航的唯一入口点,ViewController不知道其他屏幕。

子Coordinator与协调器层次结构

协调器层次结构——AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator。子协调器由父级创建并存储在childCoordinators数组中。当子协调器完成工作时,它调用父级的finish(),父级将其从childCoordinators中移除。这防止了内存泄漏:Coordinator对ViewController(通过Router)有强引用,如果不从childCoordinators中移除,对象将不会被释放。

swift
// 用于Coordinator -> Parent通信的委托
protocol AuthCoordinatorDelegate: AnyObject {
    func authCoordinatorFinished(_ coordinator: AuthCoordinator)
}

class AuthCoordinator: CoordinatorProtocol {
    weak var delegate: AuthCoordinatorDelegate?

    func finish() {
        delegate?.authCoordinatorFinished(self)
    }
}

// AppCoordinator — 父级
class AppCoordinator: AuthCoordinatorDelegate {
    func startAuthFlow() {
        let authCoordinator = AuthCoordinator(router: router)
        authCoordinator.delegate = self
        childCoordinators.append(authCoordinator)
        authCoordinator.start()
    }

    func authCoordinatorFinished(_ coordinator: AuthCoordinator) {
        childCoordinators.removeAll { $0 is AuthCoordinator }
        startMainFlow()
    }
}

管理childCoordinators——从数组中移除Coordinator是释放它的唯一方法。如果忘记移除已完成的Coordinator,它会与ViewController一起留在内存中。推荐使用:父级的didMove(toParent:)、完成时的回调或Combine publisher进行自动移除。Coordinator模式不指定通知机制——委托、closure或Combine——选择权在开发人员。

协调器之间的数据传输方式

通过委托传输数据——子Coordinator定义一个委托协议,其中包含传输结果的方法:func authCoordinator(_:didLoginWith user: User)。父级实现该协议,并在子流程完成时接收数据。这是类型安全的且明确的。缺点:每个子Coordinator都需要编写单独的协议。对于有10+个Coordinator的项目,这会导致文件数量增加。

通过Result类型传输数据——finish方法接收Result,其中Output是流程结果的泛型类型。Coordinator——带有结果关联类型的泛型。带回调的start():start(completion: @escaping (Output) -> Void)。这缩短了代码:不需要为每个协调器编写单独的协议。RxSwift/Combine:Coordinator通过PassthroughSubject/Publisher发布结果。选择取决于团队的架构方法。

方法优点缺点
Delegate类型安全,明确,独立协议协议多,样板代码多
Closure紧凑,文件少难以调试retain cycle
Combine/Rx响应式,易于组合依赖库,调试较难

共享数据层——协调器不直接传输数据,而是使用共享服务/存储库。AuthCoordinator将token保存在Keychain/UserDefaults中,ProfileCoordinator从那里读取。协调器通过共享状态(依赖注入容器)进行通信,而不是通过直接调用。这降低了协调器之间的耦合度,但产生了对共享状态的隐式依赖。

Coordinator与Router、VIPER和MVVM-C的对比

Coordinator vs Router——Router是抽象UINavigationController的Coordinator组件。Coordinator负责流程(显示哪个屏幕),Router负责机制(如何显示:push/present)。Router是“如何”,Coordinator是“什么”。Router可以在没有Coordinator的情况下使用(例如,Navigator单例),但没有Router的Coordinator只是具有不同抽象的ViewController。通常两种模式一起使用。

Coordinator vs VIPER——VIPER有一个负责导航的Wireframe组件——类似于Coordinator。在VIPER中,Wireframe是模块的一部分,Coordinator是模块之上的独立层。VIPER模块(View-Interactor-Presenter-Entity-Router)将导航作为模块的一部分。Coordinator相对于模块是外部的:它创建并连接模块,但不属于模块的组成部分。Coordinator在不同的流程中重用屏幕时更灵活。

MVVM-C——MVVM与Coordinator的扩展。ViewModel不直接知道Coordinator——ViewController通过ViewModel委托导航,ViewModel通过协议调用协调器。MVVM-C——使用SwiftUI的iOS项目的标准方法:Coordinator管理NavigationStack或fullScreenCover,ViewModel通过发布状态来调用协调器。Apple不推荐在SwiftUI中使用Coordinator——NavigationStack和NavigationPath是内置的导航机制。

swift
// MVVM-C:ViewModel通过协议调用Coordinator
protocol AuthNavigationProtocol: AnyObject {
    func showMainScreen()
    func showForgotPassword()
}

class AuthViewModel: ObservableObject {
    weak var navigation: AuthNavigationProtocol?

    func loginTapped() {
        // 逻辑...
        navigation?.showMainScreen()
    }
}

常见问题

SwiftUI需要Coordinator吗?

对于SwiftUI,内置导航(NavigationStack、NavigationPath)通常可以替代Coordinator。Apple推荐基于路径的导航。Coordinator对于具有深层条件的复杂流程(根据角色决定引导-登录-主屏幕)有意义。对于简单的SwiftUI应用程序,Coordinator是多余的——请使用NavigationPath。

Coordinator就是Router吗?

不,它们是不同的模式。Coordinator管理导航流程:决定显示哪个屏幕,创建ViewController并连接它们。Router——UINavigationController的抽象:push、present、pop、dismiss。Coordinator使用Router来执行导航。在某些实现中,Router包含Coordinator的逻辑(Router-per-screen),但这是对原始模式的偏离。

如何避免Coordinator中的retain cycle?

两个主要泄漏点:childCoordinators(父级持有子级,忘记移除)和Router(UINavigationController持有ViewController)。解决方案:始终在finish()时从数组中移除子Coordinator。对delegate使用weak引用。对于Router——如果已经在window层次结构中,不要持有对UINavigationController的强引用。测试Coordinator的deinit。

什么时候Coordinator是多余的?

对于具有3-5个屏幕的应用程序,Coordinator是多余的——segue或简单的navigationController.pushViewController更简单。对于使用NavigationStack的SwiftUI应用程序——也是多余的。Coordinator适用于具有15+个屏幕、复杂流程(带有分支的引导、带密码恢复的授权)和混合UIKit/SwiftUI的项目。

如何测试Coordinator?

Mock Router——检查哪些方法被调用以及使用什么参数。检查childCoordinators:start()后数组不为空,finish()后为空。Coordinator在没有UI的情况下进行测试:Router是一个协议,其mock不需要UIKit。对于异步流程,使用XCTestExpectation。在Android中类似——使用mock导航测试NavigationController和NavHost。

总结

  • Coordinator——将切换从ViewController转移到独立类的导航模式
  • Router——Coordinator使用的UINavigationController抽象
  • 层次结构——父级和子级Coordinator,带有结果委托
  • MVVM-C——使用Coordinator的UIKit项目的标准方法
  • SwiftUI——通过NavigationStack的内置导航替代了Coordinator
  • 测试——Coordinator通过mock Router在没有UIKit的情况下进行测试

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读