Coordinator: kulcsfogalmak, a koordinátor minta iOS-navigációhoz

Szerző: IT Sectr Megjelenés: 2026-02-18 Olvasási idő: 9 perc

Coordinator (koordinátor) — egy architekturális navigációs minta, amely a képernyők közötti átmenetek logikáját a ViewController-ból külön osztályokba helyezi át. A mintát Soroush Khanlou javasolta 2015-ben, és széles körben elterjedt az iOS közösségben. A Coordinator kezeli az alkalmazás flow-ját: létrehozza és megjeleníti a ViewController-t, adatokat továbbít a képernyők között és feldolgozza a flow befejezését. A minta megoldja a Massive View Controller problémát azáltal, hogy kiveszi a navigációt a kontrollerből. Bővebben — a eredeti Coordinator cikkben.

Főbb pontok

  • Coordinator — navigációs minta, amely az átmenetek logikáját kiveszi a ViewController-ból
  • Felelősségi körök szétválasztása — ViewController kezeli az UI-t, Coordinator — a navigációt
  • Router — a Coordinator segédkomponense a UINavigationController absztrakciójához
  • Flow — képernyők sorozata, amelyet egy Coordinator kezel (pl. onboarding)
  • Delegálás — a gyermek koordinátorok jelentést tesznek a szülőnek delegate/protocol segítségével

Mi az a Coordinator: a navigációs minta lényege

Coordinator — egy minta, amely átveszi a navigáció felelősségét egy iOS alkalmazásban. A szabványos UIKit-ben a ViewController maga kezeli az átmeneteket: present, push, show segue — az összes navigációs metódus az UIViewController-ből kerül meghívásra. A Coordinator kiveszi ezt a logikát: a ViewController jelent egy eseményt (pl. „a felhasználó megnyomta a bejelentkezés gombot”), a Coordinator eldönti, melyik képernyőt mutassa meg következőként. A ViewController csak az UI-logikával marad, és a navigációt a koordinátorra delegálja.

A minta szerkezete — CoordinatorProtocol start() és finish() metódusokkal. start() — a flow kezdete: az első ViewController létrehozása és megjelenítése. finish() — a flow befejezése a szülő koordinátor értesítésével. Router — egy burkoló a UINavigationController (vagy UISplitViewController) körül, amely show, push, pop, dismiss metódusokat biztosít. A koordinátor nem közvetlenül a UINavigationController-rel dolgozik — csak a Router-en keresztül. Ez lehetővé teszi a navigáció tesztelését és az UI-keretrendszer cseréjét.

KomponensSzerepPélda
CoordinatorNavigációs flow kezeléseAuthCoordinator, ProfileCoordinator
RouterAbsztrakció a UINavigationController fölöttpush, present, pop, dismiss
ViewControllerUI + események delegálása a Coordinator-nakLoginViewController.delegate

Problémák, amelyeket a Coordinator megold — Massive View Controller (a navigáció — a kontroller elburjánzásának gyakori oka). A szabványos UIKit-ben a ViewController tartalmazza a prepareForSegue-t, navigációs delegáltakat, unwind segues feldolgozást. A Coordinator ezt megszünteti. A Segue a storyboard-ban — statikus kapcsolat a képernyők között, a Coordinator dinamikus navigációt biztosít feltételekkel. A navigáció tesztelése lehetővé válik: a Coordinator tesztelhető UI nélkül a Router-hívások sorrendjének ellenőrzésével.

Coordinator Swift-ben: implementáció Router és Flow segítségével

Alap Coordinator Swift-ben — egy protokoll társított típussal a Router számára és start/finish metódusokkal. Router — egy protokoll, amely absztrahálja a UINavigationController-t. A Router konkrét implementációja becsomagolja a UINavigationController-t és delegálja neki a metódusokat. A Coordinator a Router-t az init-ben fogadja és navigációra használja. A gyermek koordinátorok a childCoordinators tömbben tárolódnak az életciklus kezeléséhez.

swift
// Router — navigáció absztrakció
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 kezelés
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 létrehozása az AppDelegate/SceneDelegate-ben — az AppDelegate vagy SceneDelegate létrehozza a UINavigationController-t, becsomagolja NavigationRouter-be, létrehozza a gyökér Coordinator-t (AppCoordinator) és meghívja a start()-ot. Az AppCoordinator eldönti, hogy megjelenítse-e az onboarding-ot, bejelentkezést vagy a főképernyőt — az alkalmazás állapotától függően. Coordinator — az egyetlen belépési pont a navigációhoz, a ViewController nem tud más képernyőkről.

Gyermek Coordinator és a koordinátorok hierarchiája

Koordinátorok hierarchiája — AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator. A gyermek koordinátort a szülő hozza létre és tárolja a childCoordinators tömbben. Amikor a gyermek koordinátor befejezi a munkáját, meghívja a finish()-t a szülőnél, és a szülő eltávolítja a childCoordinators-ből. Ez megakadályozza a memóriaszivárgást: a Coordinator erős hivatkozással rendelkezik a ViewController-re (a Router-en keresztül), és anélkül, hogy eltávolítanák a childCoordinators-ből, az objektum nem szabadul fel.

swift
// Delegált a Coordinator -> Parent kapcsolathoz
protocol AuthCoordinatorDelegate: AnyObject {
    func authCoordinatorFinished(_ coordinator: AuthCoordinator)
}

class AuthCoordinator: CoordinatorProtocol {
    weak var delegate: AuthCoordinatorDelegate?

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

// AppCoordinator — szülő
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 kezelése — a Coordinator eltávolítása a tömbből az egyetlen módja a felszabadításának. Ha elfelejti eltávolítani a befejezett Coordinator-t, az a memóriában marad a ViewController-ekkel együtt. Javasolt: a szülő didMove(toParent:), callback a befejezéskor, vagy Combine publisher az automatikus eltávolításhoz. A Coordinator minta nem specifikálja az értesítési mechanizmust — delegate, closure vagy Combine — a választás a fejlesztőé.

Adatátviteli módszerek koordinátorok között

Adatátvitel delegált segítségével — a gyermek Coordinator definiál egy delegate protokollt metódusokkal, amelyeken keresztül az eredmények továbbítódnak: func authCoordinator(_:didLoginWith user: User). A szülő implementálja a protokollt, és adatokat kap a gyermek flow befejezésekor. Ez típusbiztos és explicit. Hátrány: minden gyermek Coordinator-hoz külön protokollt kell írni. 10+ Coordinator-t tartalmazó projekteknél ez a fájlok számának növekedéséhez vezet.

Adatátvitel Result típus segítségével — a finish metódus Result típust fogad, ahol Output a flow eredményének generikus típusa. Coordinator — generikus a kapcsolódó eredménytípussal. start() callback-kel: start(completion: @escaping (Output) -> Void). Ez lerövidíti a kódot: nem kell külön protokollt írni minden koordinátorhoz. RxSwift/Combine: a Coordinator közzéteszi az eredményt PassthroughSubject/Publisher segítségével. A választás a csapat architekturális megközelítésétől függ.

MódszerElőnyökHátrányok
DelegateTípusbiztos, explicit, külön protokollokSok protokoll, sok boilerplate
ClosureKompakt, kevesebb fájlNehéz debugolni a retain cycle-t
Combine/RxReaktív, könnyen kombinálhatóFüggőség a könyvtártól, nehezebb Debug

Megosztott adatréteg — A Coordinator-ok nem közvetlenül továbbítanak adatokat, hanem közös szolgáltatást/adattárat használnak. Az AuthCoordinator elmenti a token-t a Keychain/UserDefaults-ba, a ProfileCoordinator onnan olvassa. A Coordinator-ok közös állapoton (Dependency Injection konténer) keresztül kommunikálnak, nem közvetlen hívásokkal. Ez csökkenti a Coordinator-ok összekapcsoltságát, de implicit függőségeket hoz létre a közös állapottól.

Coordinator összehasonlítása Router, VIPER és MVVM-C mintákkal

Coordinator vs Router — A Router a Coordinator egyik komponense, amely absztrahálja a UINavigationController-t. A Coordinator felelős a flow-ért (melyik képernyőt mutassa), a Router — a mechanikáért (hogyan mutassa: push/present). A Router a „hogyan”, a Coordinator a „mit”. A Router használható Coordinator nélkül (pl. Navigator-szingleton), de a Coordinator Router nélkül — csak egy ViewController más absztrakcióval. Általában mindkét mintát együtt használják.

Coordinator vs VIPER — A VIPER rendelkezik egy Wireframe komponenssel, amely a navigációért felelős — a Coordinator analógja. A VIPER-ben a Wireframe a modul része, a Coordinator — külön réteg a modulok fölött. A VIPER modul (View-Interactor-Presenter-Entity-Router) tartalmazza a navigációt a modul részeként. A Coordinator külső a modulokhoz képest: létrehozza és összekapcsolja a modulokat, de nem része azok összetételének. A Coordinator rugalmasabb a képernyők különböző flow-kban való újrafelhasználásához.

MVVM-C — az MVVM kiterjesztése Coordinator-ral. A ViewModel nem ismeri közvetlenül a Coordinator-t — a ViewController a ViewModel-en keresztül delegálja a navigációt, a ViewModel egy protokollon keresztül hívja meg a koordinátort. MVVM-C — a szabványos megközelítés iOS projektekhez SwiftUI-vel: a Coordinator kezeli a NavigationStack-t vagy a fullScreenCover-t, a ViewModel állapot közzétételével hívja meg a koordinátort. Az Apple nem javasolja a Coordinator-t SwiftUI-hez — a NavigationStack és NavigationPath beépített navigációs mechanizmusok.

swift
// MVVM-C: ViewModel protokollon keresztül hívja a Coordinator-t
protocol AuthNavigationProtocol: AnyObject {
    func showMainScreen()
    func showForgotPassword()
}

class AuthViewModel: ObservableObject {
    weak var navigation: AuthNavigationProtocol?

    func loginTapped() {
        // logika...
        navigation?.showMainScreen()
    }
}

Gyakran ismételt kérdések

Szükséges a Coordinator a SwiftUI-hoz?

A SwiftUI esetében a beépített navigáció (NavigationStack, NavigationPath) gyakran helyettesíti a Coordinator-t. Az Apple a path-alapú navigációt ajánlja. A Coordinator összetett, mély feltételekkel rendelkező flow-k esetén van értelme (onboarding-bejelentkezés-főképernyő a szereptől függően). Egyszerű SwiftUI alkalmazásokhoz a Coordinator felesleges — használja a NavigationPath-et.

A Coordinator ugyanaz, mint a Router?

Nem, ezek különböző minták. A Coordinator kezeli a navigációs flow-t: eldönti, melyik képernyőt mutassa, létrehozza a ViewController-eket és összekapcsolja őket. A Router — absztrakció a UINavigationController fölött: push, present, pop, dismiss. A Coordinator a Router-t használja a navigáció végrehajtásához. Néhány implementációban a Router tartalmazza a Coordinator logikáját (Router-per-screen), de ez eltérés az eredeti mintától.

Hogyan kerüljük el a retain cycle-t a Coordinator-ban?

Két fő szivárgási hely: childCoordinators (a szülő tartja a gyermeket, elfelejti eltávolítani) és Router (a UINavigationController tartja a ViewController-t). Megoldás: mindig távolítsa el a gyermek Coordinator-t a tömbből finish()-nél. Használjon weak referenciát a delegate-hez. Router esetén — ne tartson erős referenciát a UINavigationController-re, ha már a window hierarchiában van. Tesztelje a Coordinator deinit-jét.

Mikor felesleges a Coordinator?

3-5 képernyős alkalmazásokhoz a Coordinator felesleges — a segue vagy egyszerű navigationController.pushViewController egyszerűbb. NavigationStack-kel rendelkező SwiftUI alkalmazásokhoz is felesleges. A Coordinator 15+ képernyős, összetett flow-val (elágazó onboarding, jelszó-visszaállítással rendelkező hitelesítés) és vegyes UIKit/SwiftUI projektekhez indokolt.

Hogyan teszteljük a Coordinator-t?

Mock Router — annak ellenőrzése, hogy mely metódusok milyen paraméterekkel kerülnek meghívásra. childCoordinators ellenőrzése: start() után a tömb nem üres, finish() után — üres. A Coordinator UI nélkül tesztelhető: a Router egy protokoll, a mock-ja nem igényel UIKit-et. Használjon XCTestExpectation-t aszinkron flow-hoz. Android-ban analóg — a NavigationController és NavHost tesztelése mock-navigációval.

Összefoglaló

  • Coordinator — navigációs minta, amely az átmeneteket a ViewController-ból külön osztályba helyezi át
  • Router — a Coordinator által használt UINavigationController absztrakció
  • Hierarchia — szülő és gyermek Coordinator az eredmények delegálásával
  • MVVM-C — szabványos megközelítés UIKit projektekhez Coordinator-ral
  • SwiftUI — beépített navigáció NavigationStack-en keresztül helyettesíti a Coordinator-t
  • Tesztelés — a Coordinator mock Router-en keresztül, UIKit nélkül tesztelhető

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is