Coordinator: مفاهیم کلیدی، الگوی هماهنگ‌کننده برای ناوبری iOS

نویسنده: IT Sectr منتشر شده: 2026-02-18 زمان مطالعه: 9 دقیقه

Coordinator (هماهنگ‌کننده) — الگوی معماری ناوبری است که منطق انتقال بین صفحه‌ها را از ViewController به کلاس‌های جداگانه منتقل می‌کند. این الگو توسط Soroush Khanlou در سال ۲۰۱۵ ارائه شد و در جامعه iOS گسترش یافت. Coordinator جریان (flow) برنامه را مدیریت می‌کند: ViewController را ایجاد و نمایش می‌دهد، داده‌ها را بین صفحه‌ها منتقل می‌کند و پایان flow را پردازش می‌کند. الگو مشکل 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 باقی می‌ماند و ناوبری را به هماهنگ‌کننده واگذار می‌کند.

ساختار الگو — CoordinatorProtocol با متدهای start() و finish(). start() — شروع flow: ایجاد اولین ViewController و نمایش آن. finish() — پایان flow با اعلان به هماهنگ‌کننده والد. Router — پوششی بر UINavigationController (یا UISplitViewController) که متدهای show، push، pop، dismiss را ارائه می‌دهد. هماهنگ‌کننده مستقیماً با UINavigationController کار نمی‌کند — فقط از طریق Router. این امکان تست ناوبری و تغییر فریمورک UI را فراهم می‌کند.

مؤلفهنقشمثال
Coordinatorمدیریت flow ناوبریAuthCoordinator, ProfileCoordinator
Routerانتزاع بر UINavigationControllerpush, present, pop, dismiss
ViewControllerUI + واگذاری رویدادها به CoordinatorLoginViewController.delegate

مشکلاتی که Coordinator حل می‌کند — Massive View Controller (ناوبری — دلیل رایج بزرگ شدن کنترلر). در UIKit استاندارد، ViewController شامل prepareForSegue، نمایندگان ناوبری، پردازش unwind segues است. Coordinator این را حذف می‌کند. Segue در storyboard — ارتباط ایستا بین صفحه‌ها، Coordinator ناوبری پویا با شرایط را فراهم می‌کند. تست ناوبری ممکن می‌شود: می‌توان Coordinator را بدون UI با بررسی توالی فراخوانی‌های Router تست کرد.

Coordinator در Swift: پیاده‌سازی با Router و Flow

Coordinator پایه در Swift — پروتکل با نوع وابسته برای Router و متدهای start/finish. Router — پروتکلی که UINavigationController را انتزاع می‌کند. پیاده‌سازی مشخص Router، UINavigationController را می‌پوشاند و متدها را به آن واگذار می‌کند. Coordinator Router را در init می‌پذیرد و برای ناوبری از آن استفاده می‌کند. هماهنگ‌کننده‌های فرزند در آرایه 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 — مدیریت flow
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)
    }
}

ایجاد Coordinator در AppDelegate/SceneDelegate — 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:) والد، callback در پایان یا ناشر Combine برای حذف خودکار. الگوی Coordinator مکانیسم اعلان را مشخص نمی‌کند — نماینده، closure یا Combine — انتخاب با توسعه‌دهنده است.

روش‌های انتقال داده بین هماهنگ‌کننده‌ها

انتقال داده از طریق نماینده — Coordinator فرزند پروتکل نماینده را با متدهایی که نتایج از طریق آنها منتقل می‌شوند تعریف می‌کند: func authCoordinator(_:didLoginWith user: User). والد پروتکل را پیاده‌سازی می‌کند و در پایان flow فرزند داده‌ها را دریافت می‌کند. این روش نوعاً ایمن و آشکار است. عیب: برای هر Coordinator فرزند باید پروتکل جداگانه نوشته شود. برای پروژه‌های با ۱۰+ Coordinator این باعث افزایش تعداد فایل‌ها می‌شود.

انتقال داده از طریق نوع Result — متد finish Result را می‌پذیرد، جایی که Output نوع جنریک نتیجه flow است. Coordinator — جنریک با نوع وابسته نتیجه. start() با callback: start(completion: @escaping (Output) -> Void). این کار کد را کوتاه می‌کند: نیازی به نوشتن پروتکل جداگانه برای هر هماهنگ‌کننده نیست. RxSwift/Combine: Coordinator نتیجه را از طریق PassthroughSubject/Publisher منتشر می‌کند. انتخاب بستگی به رویکرد معماری تیم دارد.

روشمزایامعایب
Delegateنوعاً ایمن، آشکار، پروتکل‌های جداگانهپروتکل‌های زیاد، کد تکراری زیاد
Closureفشرده، فایل‌های کمتراشکال‌زدایی retain cycle دشوار است
Combine/Rxواکنشی، ترکیب آسانوابستگی به کتابخانه، Debug دشوارتر

لایه داده مشترک — Coordinatorها داده را مستقیماً منتقل نمی‌کنند، بلکه از سرویس/مخزن مشترک استفاده می‌کنند. AuthCoordinator توکن را در Keychain/UserDefaults ذخیره می‌کند، ProfileCoordinator از آنجا می‌خواند. Coordinatorها از طریق وضعیت مشترک (ظرف تزریق وابستگی) ارتباط برقرار می‌کنند، نه از طریق فراخوانی مستقیم. این کار وابستگی Coordinatorها را کاهش می‌دهد، اما وابستگی‌های ضمنی به وضعیت مشترک ایجاد می‌کند.

مقایسه Coordinator با Router، VIPER و MVVM-C

Coordinator vs Router — Router مؤلفه Coordinator است که UINavigationController را انتزاع می‌کند. Coordinator مسئول flow است (کدام صفحه نمایش داده شود)، Router — مسئول مکانیک (چگونه نمایش دهد: push/present). Router «چگونگی» است، Coordinator «چیستی». می‌توان از Router بدون Coordinator استفاده کرد (مثلاً Navigator-تکتن)، اما Coordinator بدون Router — فقط ViewController با انتزاعی دیگر. معمولاً هر دو الگو با هم استفاده می‌شوند.

Coordinator vs VIPER — VIPER مؤلفه Wireframe را دارد که مسئول ناوبری است — مشابه Coordinator. در VIPER، Wireframe بخشی از ماژول است، Coordinator — لایه جداگانه‌ای بالای ماژول‌ها. ماژول VIPER (View-Interactor-Presenter-Entity-Router) ناوبری را به عنوان بخشی از ماژول شامل می‌شود. Coordinator نسبت به ماژول‌ها خارجی است: ماژول‌ها را ایجاد و متصل می‌کند، اما جزئی از آنها نیست. Coordinator برای استفاده مجدد از صفحه‌ها در flowهای مختلف انعطاف‌پذیرتر است.

MVVM-C — گسترش MVVM با Coordinator. ViewModel مستقیماً Coordinator را نمی‌شناسد — ViewController ناوبری را از طریق ViewModel واگذار می‌کند، ViewModel هماهنگ‌کننده را از طریق پروتکل فراخوانی می‌کند. MVVM-C — رویکرد استاندارد برای پروژه‌های iOS با SwiftUI: Coordinator NavigationStack یا fullScreenCover را مدیریت می‌کند، ViewModel با انتشار وضعیت، هماهنگ‌کننده را فراخوانی می‌کند. Apple Coordinator را برای SwiftUI توصیه نمی‌کند — 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()
    }
}

سؤالات متداول

آیا Coordinator برای SwiftUI لازم است؟

برای SwiftUI ناوبری داخلی (NavigationStack, NavigationPath) اغلب جایگزین Coordinator می‌شود. Apple ناوبری مبتنی بر مسیر (path-based) را توصیه می‌کند. Coordinator برای flowهای پیچیده با شرایط عمیق (آموزش-ورود-صفحه اصلی بسته به نقش) معنا دارد. برای برنامه‌های ساده SwiftUI Coordinator اضافی است — از NavigationPath استفاده کنید.

آیا Coordinator همان Router است؟

خیر، این دو الگوی متفاوت هستند. Coordinator flow ناوبری را مدیریت می‌کند: تصمیم می‌گیرد کدام صفحه نمایش داده شود، ViewController ایجاد می‌کند و آنها را متصل می‌کند. Router — انتزاعی بر UINavigationController: push, present, pop, dismiss. Coordinator برای اجرای ناوبری از Router استفاده می‌کند. در برخی پیاده‌سازی‌ها Router شامل منطق Coordinator است (Router-per-screen)، اما این انحراف از الگوی اصلی است.

چگونه از retain cycle در Coordinator جلوگیری کنیم؟

دو مکان اصلی نشت: childCoordinators (والد فرزند را نگه می‌دارد و حذف را فراموش می‌کند) و Router (UINavigationController ViewController را نگه می‌دارد). راه‌حل: همیشه Coordinator فرزند را در finish() از آرایه حذف کنید. برای delegate از ارجاع weak استفاده کنید. برای Router — ارجاع قوی به UINavigationController نگه ندارید اگر در سلسله‌مراتب window است. deinit Coordinator را تست کنید.

چه زمانی Coordinator اضافی است؟

برای برنامه‌های با ۳-۵ صفحه Coordinator اضافی است — segue یا navigationController.pushViewController ساده‌تر است. برای برنامه‌های SwiftUI با NavigationStack نیز اضافی است. Coordinator برای برنامه‌های با ۱۵+ صفحه، flowهای پیچیده (آموزش با شاخه‌ها، احراز هویت با بازیابی رمز عبور) و پروژه‌های ترکیبی UIKit/SwiftUI توجیه‌پذیر است.

چگونه Coordinator را تست کنیم؟

Mock Router — بررسی کدام متدها با چه پارامترهایی فراخوانی می‌شوند. بررسی childCoordinators: بعد از start() آرایه خالی نیست، بعد از finish() — خالی است. Coordinator بدون UI تست می‌شود: Router یک پروتکل است، mock آن به UIKit نیاز ندارد. برای flow ناهمگام از XCTestExpectation استفاده کنید. در اندروید مشابه — تست NavigationController و NavHost با mock-ناوبری.

خلاصه

  • Coordinator — الگوی ناوبری که انتقال‌ها را از ViewController به کلاس جداگانه منتقل می‌کند
  • Router — انتزاع UINavigationController که توسط Coordinator استفاده می‌شود
  • سلسله‌مراتب — Coordinatorهای والد و فرزند با تفویض نتایج
  • MVVM-C — رویکرد استاندارد برای پروژه‌های UIKit با Coordinator
  • SwiftUI — ناوبری داخلی از طریق NavigationStack جایگزین Coordinator می‌شود
  • تست — Coordinator از طریق mock Router بدون UIKit تست می‌شود

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید