coordinator-pattern
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCoordinator Pattern — Expert Decisions
Coordinator模式——专家级决策指南
Expert decision frameworks for Coordinator pattern choices. Claude knows the pattern — this skill provides judgment calls for when coordinators add value and how to structure hierarchies.
为Coordinator模式的选择提供专家级决策框架。Claude精通该模式——本技能可帮助判断何时使用Coordinator能创造价值,以及如何构建其层级结构。
Decision Trees
决策树
Do You Need Coordinators?
是否需要使用Coordinators?
How many navigation flows does your app have?
├─ 1-2 simple flows
│ └─ Skip coordinators
│ NavigationStack + simple Router is enough
│
├─ 3-5 distinct flows
│ └─ Consider coordinators IF:
│ • Flows have complex branching
│ • Deep linking is required
│ • Flows need to share navigation logic
│
└─ 6+ flows or multi-team development
└─ Coordinators recommended
• Clear ownership boundaries
• Parallel development possible
• Testable navigation logicThe trap: Adding coordinators to simple apps. If your app is 5 screens with linear flow, coordinators add complexity without benefit.
你的应用有多少个导航流程?
├─ 1-2个简单流程
│ └─ 无需使用Coordinators
│ 使用NavigationStack + 简单Router即可满足需求
│
├─ 3-5个独立流程
│ └─ 满足以下条件时可考虑使用Coordinators:
│ • 流程存在复杂分支
│ • 需要支持深度链接
│ • 流程需共享导航逻辑
│
└─ 6个及以上流程或多团队开发场景
└─ 推荐使用Coordinators
• 清晰的职责边界
• 支持并行开发
• 导航逻辑可测试误区:在简单应用中强行引入Coordinators。如果你的应用只有5个页面且是线性流程,Coordinators只会增加不必要的复杂度。
Coordinator Hierarchy Design
Coordinator层级设计
Does this flow need to manage sub-flows?
├─ NO (leaf coordinator)
│ └─ Simple coordinator
│ Owns NavigationPath, creates views
│
└─ YES (has sub-flows)
└─ Parent coordinator
Manages childCoordinators array
Delegates to child for sub-flows当前流程是否需要管理子流程?
├─ 不需要(叶子节点Coordinator)
│ └─ 简单Coordinator
│ 持有NavigationPath,负责创建视图
│
└─ 需要(包含子流程)
└─ 父Coordinator
管理childCoordinators数组
将子流程委托给子Coordinator处理SwiftUI vs UIKit Coordinator
SwiftUI与UIKit中的Coordinator差异
Which UI framework?
├─ SwiftUI
│ └─ Coordinator as ObservableObject
│ • Owns NavigationPath
│ • @ViewBuilder for destinations
│ • Pass via EnvironmentObject or explicit injection
│
└─ UIKit
└─ Coordinator owns UINavigationController
• Creates and pushes ViewControllers
• Uses delegation for flow completion
• Manages childCoordinators manually使用的UI框架是?
├─ SwiftUI
│ └─ Coordinator作为ObservableObject
│ • 持有NavigationPath
│ • 使用@ViewBuilder定义目标页面
│ • 通过EnvironmentObject或显式注入方式传递
│
└─ UIKit
└─ Coordinator持有UINavigationController
• 创建并推送ViewControllers
• 使用代理处理流程完成
• 手动管理childCoordinatorsFlow Completion Strategy
流程完成策略
How does a flow end?
├─ Success (user completed task)
│ └─ Delegate method with result
│ coordinator.didCompleteLogin(user: user)
│
├─ Cancellation (user backed out)
│ └─ Delegate method without result
│ coordinator.didCancelLogin()
│
└─ Automatic (flow naturally ends)
└─ Parent removes child automatically
No explicit completion needed流程如何结束?
├─ 成功(用户完成任务)
│ └─ 带返回结果的代理方法
│ coordinator.didCompleteLogin(user: user)
│
├─ 取消(用户退出流程)
│ └─ 不带返回结果的代理方法
│ coordinator.didCancelLogin()
│
└─ 自动结束(流程自然终止)
└─ 父Coordinator自动移除子Coordinator
无需显式的完成回调NEVER Do
绝对禁忌
Child Coordinator Lifecycle
子Coordinator生命周期
NEVER forget to remove child coordinators:
swift
// ❌ Memory leak — child coordinator retained forever
final class ParentCoordinator {
var childCoordinators: [Coordinator] = []
func startLoginFlow() {
let loginCoordinator = LoginCoordinator()
childCoordinators.append(loginCoordinator)
loginCoordinator.start()
// Never removed! Leaks.
}
}
// ✅ Remove child on flow completion
final class ParentCoordinator: LoginCoordinatorDelegate {
var childCoordinators: [Coordinator] = []
func startLoginFlow() {
let loginCoordinator = LoginCoordinator()
loginCoordinator.delegate = self
childCoordinators.append(loginCoordinator)
loginCoordinator.start()
}
func loginCoordinatorDidFinish(_ coordinator: LoginCoordinator) {
childCoordinators.removeAll { $0 === coordinator }
}
}NEVER use strong parent references:
swift
// ❌ Retain cycle — coordinator never deallocates
final class ChildCoordinator {
var parent: ParentCoordinator // Strong reference!
}
// ✅ Weak parent or delegate
final class ChildCoordinator {
weak var delegate: ChildCoordinatorDelegate?
// OR
weak var parent: ParentCoordinator?
}绝对不要忘记移除子Coordinators:
swift
// ❌ 内存泄漏——子Coordinator会被永久持有
final class ParentCoordinator {
var childCoordinators: [Coordinator] = []
func startLoginFlow() {
let loginCoordinator = LoginCoordinator()
childCoordinators.append(loginCoordinator)
loginCoordinator.start()
// 从未移除!导致内存泄漏。
}
}
// ✅ 流程完成时移除子Coordinator
final class ParentCoordinator: LoginCoordinatorDelegate {
var childCoordinators: [Coordinator] = []
func startLoginFlow() {
let loginCoordinator = LoginCoordinator()
loginCoordinator.delegate = self
childCoordinators.append(loginCoordinator)
loginCoordinator.start()
}
func loginCoordinatorDidFinish(_ coordinator: LoginCoordinator) {
childCoordinators.removeAll { $0 === coordinator }
}
}绝对不要使用强引用指向父Coordinator:
swift
// ❌ 循环引用——Coordinator永远无法被释放
final class ChildCoordinator {
var parent: ParentCoordinator // 强引用!
}
// ✅ 使用弱引用或代理
final class ChildCoordinator {
weak var delegate: ChildCoordinatorDelegate?
// 或者
weak var parent: ParentCoordinator?
}Coordinator Responsibilities
Coordinator职责边界
NEVER put business logic in coordinators:
swift
// ❌ Coordinator doing business logic
final class CheckoutCoordinator {
func completeOrder() async {
// Business logic leaked into coordinator!
let total = cart.items.reduce(0) { $0 + $1.price }
let tax = total * 0.08
try await paymentService.charge(total + tax)
}
}
// ✅ Coordinator orchestrates, ViewModel/UseCase handles logic
final class CheckoutCoordinator {
func showCheckout() {
let viewModel = CheckoutViewModel(
cartService: container.cartService,
paymentService: container.paymentService
)
// ViewModel handles business logic
}
}NEVER let views know about coordinator hierarchy:
swift
// ❌ View knows about parent coordinator
struct LoginView: View {
let coordinator: LoginCoordinator
var body: some View {
Button("Done") {
coordinator.parent?.childDidFinish(coordinator) // Wrong!
}
}
}
// ✅ View only knows its immediate coordinator
struct LoginView: View {
let coordinator: LoginCoordinator
var body: some View {
Button("Done") {
coordinator.completeLogin() // Coordinator handles delegation
}
}
}绝对不要在Coordinator中编写业务逻辑:
swift
// ❌ Coordinator处理业务逻辑(错误做法)
final class CheckoutCoordinator {
func completeOrder() async {
// 业务逻辑侵入到Coordinator中!
let total = cart.items.reduce(0) { $0 + $1.price }
let tax = total * 0.08
try await paymentService.charge(total + tax)
}
}
// ✅ Coordinator仅负责编排,业务逻辑由ViewModel/UseCase处理
final class CheckoutCoordinator {
func showCheckout() {
let viewModel = CheckoutViewModel(
cartService: container.cartService,
paymentService: container.paymentService
)
// ViewModel处理业务逻辑
}
}绝对不要让视图知晓Coordinator的层级结构:
swift
// ❌ 视图知晓父Coordinator(错误做法)
struct LoginView: View {
let coordinator: LoginCoordinator
var body: some View {
Button("完成") {
coordinator.parent?.childDidFinish(coordinator) // 错误!
}
}
}
// ✅ 视图仅知晓其直接对应的Coordinator
struct LoginView: View {
let coordinator: LoginCoordinator
var body: some View {
Button("完成") {
coordinator.completeLogin() // 由Coordinator处理代理逻辑
}
}
}SwiftUI-Specific
SwiftUI专属禁忌
NEVER create coordinators as @StateObject in child views:
swift
// ❌ New coordinator created on every parent rebuild
struct ParentView: View {
var body: some View {
ChildView() // Child creates its own coordinator
}
}
struct ChildView: View {
@StateObject var coordinator = ChildCoordinator() // Wrong!
}
// ✅ Parent creates and owns coordinator
struct ParentView: View {
@StateObject var childCoordinator = ChildCoordinator()
var body: some View {
ChildView(coordinator: childCoordinator)
}
}NEVER use NavigationLink directly when using coordinators:
swift
// ❌ Bypasses coordinator — navigation untracked
struct UserListView: View {
var body: some View {
NavigationLink("User") {
UserDetailView() // Coordinator doesn't know about this!
}
}
}
// ✅ Delegate navigation to coordinator
struct UserListView: View {
@ObservedObject var coordinator: UsersCoordinator
var body: some View {
Button("User") {
coordinator.showUserDetail(userId: "123")
}
}
}绝对不要在子视图中使用@StateObject创建Coordinator:
swift
// ❌ 父视图重建时会创建新的Coordinator
struct ParentView: View {
var body: some View {
ChildView() // 子视图自行创建Coordinator
}
}
struct ChildView: View {
@StateObject var coordinator = ChildCoordinator() // 错误!
}
// ✅ 由父视图创建并持有Coordinator
struct ParentView: View {
@StateObject var childCoordinator = ChildCoordinator()
var body: some View {
ChildView(coordinator: childCoordinator)
}
}绝对不要在使用Coordinators时直接使用NavigationLink:
swift
// ❌ 绕过Coordinator——导航行为无法被追踪
struct UserListView: View {
var body: some View {
NavigationLink("用户") {
UserDetailView() // Coordinator无法感知此导航!
}
}
}
// ✅ 将导航委托给Coordinator处理
struct UserListView: View {
@ObservedObject var coordinator: UsersCoordinator
var body: some View {
Button("用户") {
coordinator.showUserDetail(userId: "123")
}
}
}Essential Patterns
核心模式
SwiftUI Coordinator Protocol
SwiftUI Coordinator协议
swift
@MainActor
protocol Coordinator: ObservableObject {
associatedtype Route: Hashable
var path: NavigationPath { get set }
func start() -> AnyView
func navigate(to route: Route)
func pop()
func popToRoot()
}
extension Coordinator {
func pop() {
guard !path.isEmpty else { return }
path.removeLast()
}
func popToRoot() {
path = NavigationPath()
}
}swift
@MainActor
protocol Coordinator: ObservableObject {
associatedtype Route: Hashable
var path: NavigationPath { get set }
func start() -> AnyView
func navigate(to route: Route)
func pop()
func popToRoot()
}
extension Coordinator {
func pop() {
guard !path.isEmpty else { return }
path.removeLast()
}
func popToRoot() {
path = NavigationPath()
}
}Parent-Child Coordinator
父子Coordinator模式
swift
@MainActor
protocol ParentCoordinatorProtocol: AnyObject {
var childCoordinators: [any Coordinator] { get set }
func addChild(_ coordinator: any Coordinator)
func removeChild(_ coordinator: any Coordinator)
}
extension ParentCoordinatorProtocol {
func addChild(_ coordinator: any Coordinator) {
childCoordinators.append(coordinator)
}
func removeChild(_ coordinator: any Coordinator) {
childCoordinators.removeAll { $0 === coordinator as AnyObject }
}
}
// Tab coordinator managing child coordinators
@MainActor
final class TabCoordinator: ParentCoordinatorProtocol, ObservableObject {
var childCoordinators: [any Coordinator] = []
lazy var homeCoordinator: HomeCoordinator = {
let coordinator = HomeCoordinator()
coordinator.parent = self
addChild(coordinator)
return coordinator
}()
lazy var profileCoordinator: ProfileCoordinator = {
let coordinator = ProfileCoordinator()
coordinator.parent = self
addChild(coordinator)
return coordinator
}()
}swift
@MainActor
protocol ParentCoordinatorProtocol: AnyObject {
var childCoordinators: [any Coordinator] { get set }
func addChild(_ coordinator: any Coordinator)
func removeChild(_ coordinator: any Coordinator)
}
extension ParentCoordinatorProtocol {
func addChild(_ coordinator: any Coordinator) {
childCoordinators.append(coordinator)
}
func removeChild(_ coordinator: any Coordinator) {
childCoordinators.removeAll { $0 === coordinator as AnyObject }
}
}
// 管理子Coordinators的Tab Coordinator
@MainActor
final class TabCoordinator: ParentCoordinatorProtocol, ObservableObject {
var childCoordinators: [any Coordinator] = []
lazy var homeCoordinator: HomeCoordinator = {
let coordinator = HomeCoordinator()
coordinator.parent = self
addChild(coordinator)
return coordinator
}()
lazy var profileCoordinator: ProfileCoordinator = {
let coordinator = ProfileCoordinator()
coordinator.parent = self
addChild(coordinator)
return coordinator
}()
}Flow Completion with Result
带返回结果的流程完成
swift
protocol LoginCoordinatorDelegate: AnyObject {
func loginCoordinator(_ coordinator: LoginCoordinator, didFinishWith result: LoginResult)
}
enum LoginResult {
case success(User)
case cancelled
}
@MainActor
final class LoginCoordinator: ObservableObject {
weak var delegate: LoginCoordinatorDelegate?
@Published var path = NavigationPath()
enum Route: Hashable {
case credentials
case forgotPassword
case twoFactor(email: String)
}
func completeLogin(user: User) {
delegate?.loginCoordinator(self, didFinishWith: .success(user))
}
func cancel() {
delegate?.loginCoordinator(self, didFinishWith: .cancelled)
}
}swift
protocol LoginCoordinatorDelegate: AnyObject {
func loginCoordinator(_ coordinator: LoginCoordinator, didFinishWith result: LoginResult)
}
enum LoginResult {
case success(User)
case cancelled
}
@MainActor
final class LoginCoordinator: ObservableObject {
weak var delegate: LoginCoordinatorDelegate?
@Published var path = NavigationPath()
enum Route: Hashable {
case credentials
case forgotPassword
case twoFactor(email: String)
}
func completeLogin(user: User) {
delegate?.loginCoordinator(self, didFinishWith: .success(user))
}
func cancel() {
delegate?.loginCoordinator(self, didFinishWith: .cancelled)
}
}Deep Link Integration
深度链接集成
swift
@MainActor
final class AppCoordinator: ObservableObject, ParentCoordinatorProtocol {
var childCoordinators: [any Coordinator] = []
@Published var path = NavigationPath()
func handle(deepLink: DeepLink) {
// Reset to known state
popToRoot()
childCoordinators.forEach { removeChild($0) }
// Navigate to deep link destination
switch deepLink {
case .user(let id):
navigate(to: .userList)
navigate(to: .userDetail(userId: id))
case .checkout:
let checkoutCoordinator = CheckoutCoordinator()
checkoutCoordinator.delegate = self
addChild(checkoutCoordinator)
// Present checkout flow
case .settings(let section):
navigate(to: .settings)
if let section = section {
navigate(to: .settingsSection(section))
}
}
}
}swift
@MainActor
final class AppCoordinator: ObservableObject, ParentCoordinatorProtocol {
var childCoordinators: [any Coordinator] = []
@Published var path = NavigationPath()
func handle(deepLink: DeepLink) {
// 重置到已知状态
popToRoot()
childCoordinators.forEach { removeChild($0) }
// 导航到深度链接目标页面
switch deepLink {
case .user(let id):
navigate(to: .userList)
navigate(to: .userDetail(userId: id))
case .checkout:
let checkoutCoordinator = CheckoutCoordinator()
checkoutCoordinator.delegate = self
addChild(checkoutCoordinator)
// 展示结账流程
case .settings(let section):
navigate(to: .settings)
if let section = section {
navigate(to: .settingsSection(section))
}
}
}
}Quick Reference
快速参考
Coordinator Checklist
Coordinator检查清单
- Coordinator owns NavigationPath (SwiftUI) or UINavigationController (UIKit)
- Parent-child references are weak
- Child coordinators removed on flow completion
- Views don't know about coordinator hierarchy
- Business logic stays in ViewModels/UseCases
- Deep links handled at appropriate coordinator level
- Coordinator持有NavigationPath(SwiftUI)或UINavigationController(UIKit)
- 父子Coordinator之间使用弱引用
- 流程完成时移除子Coordinators
- 视图不知晓Coordinator的层级结构
- 业务逻辑存放在ViewModels/UseCases中
- 在合适的Coordinator层级处理深度链接
When to Use Coordinators
何时使用Coordinators
| Scenario | Use Coordinator? |
|---|---|
| Simple 3-5 screen app | No — simple Router |
| Multiple independent flows | Yes |
| Deep linking required | Likely yes |
| Multi-step wizard flows | Yes |
| Cross-tab navigation | Yes |
| A/B testing navigation | Yes |
| Team-based feature ownership | Yes |
| 场景 | 是否使用Coordinator? |
|---|---|
| 简单的3-5页面应用 | 否——使用简单Router即可 |
| 多个独立流程 | 是 |
| 需要支持深度链接 | 建议使用 |
| 多步骤向导流程 | 是 |
| 跨Tab导航 | 是 |
| 导航流程的A/B测试 | 是 |
| 按团队划分功能职责 | 是 |
Red Flags
风险信号
| Smell | Problem | Fix |
|---|---|---|
| childCoordinators grows forever | Memory leak | Remove on completion |
| Strong parent reference | Retain cycle | Use weak or delegate |
| Business logic in coordinator | Wrong layer | Move to ViewModel/UseCase |
| View creates NavigationLink | Bypasses coordinator | Delegate to coordinator |
| @StateObject coordinator in child | Recreated on rebuild | Parent owns coordinator |
| Coordinator creates its own views | Can't inject dependencies | Use ViewFactory |
| 问题表现 | 潜在风险 | 修复方案 |
|---|---|---|
| childCoordinators数量持续增长 | 内存泄漏 | 流程完成时移除子Coordinator |
| 使用强引用指向父Coordinator | 循环引用 | 使用弱引用或代理模式 |
| Coordinator中包含业务逻辑 | 职责分层错误 | 将业务逻辑迁移到ViewModel/UseCase |
| 视图直接使用NavigationLink | 绕过Coordinator导致导航无法追踪 | 将导航逻辑委托给Coordinator |
| 子视图中使用@StateObject创建Coordinator | 父视图重建时Coordinator被重复创建 | 由父视图创建并持有Coordinator |
| Coordinator自行创建视图 | 无法注入依赖 | 使用ViewFactory模式 |
Coordinator vs Router
Coordinator与Router对比
| Aspect | Coordinator | Router |
|---|---|---|
| Complexity | Higher | Lower |
| Hierarchy support | Yes (parent-child) | No |
| Flow isolation | Strong | Weak |
| Testing | Excellent | Good |
| Learning curve | Steep | Gentle |
| Best for | Large apps, teams | Small-medium apps |
| 维度 | Coordinator | Router |
|---|---|---|
| 复杂度 | 较高 | 较低 |
| 层级支持 | 支持(父子结构) | 不支持 |
| 流程隔离性 | 强 | 弱 |
| 可测试性 | 优秀 | 良好 |
| 学习曲线 | 陡峭 | 平缓 |
| 适用场景 | 大型应用、多团队开发 | 中小型应用 |