Coordinator: concepte cheie, modelul coordinator pentru navigarea iOS

Autor: IT Sectr Publicat: 2026-02-18 Timp de citire: 9 min

Coordinator (coordonator) — un model arhitectural de navigare care mută logica tranzițiilor între ecrane din ViewController în clase separate. Modelul a fost propus de Soroush Khanlou în 2015 și a câștigat popularitate largă în comunitatea iOS. Coordinator gestionează flow-ul aplicației: creează și afișează ViewController, transmite date între ecrane și procesează finalizarea flow-ului. Modelul rezolvă problema Massive View Controller, eliminând navigarea din controler. Mai multe — în articolul original Coordinator.

Principalele puncte

  • Coordinator — model de navigare care mută logica tranzițiilor din ViewController
  • Separarea responsabilităților — ViewController gestionează UI, Coordinator — navigarea
  • Router — component auxiliar al Coordinator pentru abstractizarea UINavigationController
  • Flow — secvență de ecrane gestionată de un Coordinator (de exemplu, onboarding)
  • Delegare — coordonatorii copii raportează părintelui prin delegate/protocol

Ce este Coordinator: esența modelului de navigare

Coordinator — un model care preia responsabilitatea navigării într-o aplicație iOS. În UIKit standard, ViewController gestionează singur tranzițiile: present, push, show segue — toate metodele de navigare sunt apelate din UIViewController. Coordinator mută această logică: ViewController raportează un eveniment (de exemplu, „utilizatorul a apăsat butonul de autentificare”), Coordinator decide ce ecran să afișeze în continuare. ViewController rămâne doar cu logica UI și delegă navigarea coordonatorului.

Structura modelului — CoordinatorProtocol cu metodele start() și finish(). start() — începutul flow-ului: crearea primului ViewController și afișarea. finish() — finalizarea flow-ului cu notificarea coordonatorului părinte. Router — o învelitoare peste UINavigationController (sau UISplitViewController) care oferă metodele show, push, pop, dismiss. Coordonatorul nu lucrează direct cu UINavigationController — doar prin Router. Acest lucru permite testarea navigării și schimbarea framework-ului UI.

ComponentăRolExemplu
CoordinatorGestionarea flow-ului de navigareAuthCoordinator, ProfileCoordinator
RouterAbstractizare peste UINavigationControllerpush, present, pop, dismiss
ViewControllerUI + delegarea evenimentelor către CoordinatorLoginViewController.delegate

Probleme pe care le rezolvă Coordinator — Massive View Controller (navigarea — o cauză frecventă a creșterii controlerului). În UIKit standard, ViewController conține prepareForSegue, delegați de navigare, procesarea unwind segues. Coordinator elimină acest lucru. Segue în storyboard — o legătură statică între ecrane, Coordinator oferă navigare dinamică cu condiții. Testarea navigării devine posibilă: se poate testa Coordinator fără UI, verificând secvența apelurilor Router.

Coordinator în Swift: implementare cu Router și Flow

Coordinator de bază în Swift — un protocol cu un tip asociat pentru Router și metodele start/finish. Router — un protocol care abstractizează UINavigationController. Implementarea concretă a Router învelește UINavigationController și îi delegă metodele. Coordinator primește Router în init și îl folosește pentru navigare. Coordonatorii copii sunt stocați în tabloul childCoordinators pentru gestionarea ciclului de viață.

swift
// Router — abstractizare navigare
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 — gestionare 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)
    }
}

Crearea Coordinator în AppDelegate/SceneDelegate — AppDelegate sau SceneDelegate creează UINavigationController, îl învelește în NavigationRouter, creează Coordinator-ul rădăcină (AppCoordinator) și apelează start(). AppCoordinator decide să afișeze onboarding, autentificare sau ecranul principal — în funcție de starea aplicației. Coordinator — singurul punct de intrare pentru navigare, ViewController nu știe despre alte ecrane.

Coordinatori copii și ierarhia coordonatorilor

Ierarhia coordonatorilor — AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator. Coordonatorul copil este creat de părinte și stocat în tabloul childCoordinators. Când coordonatorul copil își încheie activitatea, apelează finish() la părinte, iar părintele îl elimină din childCoordinators. Acest lucru previne scurgerile de memorie: Coordinator are o referință puternică la ViewController (prin Router), iar fără eliminarea din childCoordinators obiectul nu va fi eliberat.

swift
// Delegat pentru comunicarea Coordinator -> Parent
protocol AuthCoordinatorDelegate: AnyObject {
    func authCoordinatorFinished(_ coordinator: AuthCoordinator)
}

class AuthCoordinator: CoordinatorProtocol {
    weak var delegate: AuthCoordinatorDelegate?

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

// AppCoordinator — părinte
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()
    }
}

Gestionarea childCoordinators — eliminarea Coordinator-ului din tablou este singura modalitate de a-l elibera. Dacă uiți să elimini Coordinator-ul finalizat, acesta rămâne în memorie împreună cu ViewController-urile. Recomandat: didMove(toParent:) al părintelui, callback la finalizare sau publisher Combine pentru eliminare automată. Modelul Coordinator nu specifică mecanismul de notificare — delegat, closure sau Combine — alegerea aparține dezvoltatorului.

Metode de transmitere a datelor între coordonatori

Transmiterea datelor prin delegat — Coordinatorul copil definește un protocol de delegat cu metode prin care se transmit rezultatele: func authCoordinator(_:didLoginWith user: User). Părintele implementează protocolul și primește datele la finalizarea flow-ului copil. Acest lucru este tip-sigur și explicit. Dezavantaj: pentru fiecare Coordinator copil trebuie scris un protocol separat. Pentru proiecte cu 10+ Coordonatori, aceasta duce la creșterea numărului de fișiere.

Transmiterea datelor prin tipul Result — metoda finish primește Result, unde Output este tipul generic al rezultatului flow-ului. Coordinator — un generic cu tipul asociat al rezultatului. start() cu callback: start(completion: @escaping (Output) -> Void). Acest lucru scurtează codul: nu este nevoie să scrii un protocol separat pentru fiecare coordonator. RxSwift/Combine: Coordinator publică rezultatul prin PassthroughSubject/Publisher. Alegerea depinde de abordarea arhitecturală a echipei.

MetodăAvantajeDezavantaje
DelegateTip-sigur, explicit, protocoale separateMulte protocoale, mult boilerplate
ClosureCompact, mai puține fișiereDificil de depanat retain cycle
Combine/RxReactiv, ușor de combinatDependență de bibliotecă, Debug mai dificil

Strat de date partajat — Coordinatorii nu transmit date direct, ci folosesc un serviciu/repository comun. AuthCoordinator salvează token-ul în Keychain/UserDefaults, ProfileCoordinator citește de acolo. Coordinatorii comunică prin stare partajată (container Dependency Injection), nu prin apeluri directe. Aceasta reduce cuplarea Coordinatorilor, dar creează dependențe implicite de starea partajată.

Comparația Coordinator cu Router, VIPER și MVVM-C

Coordinator vs Router — Router este o componentă a Coordinatorului care abstractizează UINavigationController. Coordinator răspunde de flow (ce ecran să afișeze), Router — de mecanică (cum să afișeze: push/present). Router este „cum”, Coordinator este „ce”. Poți folosi Router fără Coordinator (de exemplu, Navigator-singleton), dar Coordinator fără Router — doar un ViewController cu o altă abstractizare. De obicei, ambele modele sunt folosite împreună.

Coordinator vs VIPER — VIPER are componenta Wireframe responsabilă de navigare — analogul Coordinatorului. În VIPER, Wireface face parte din modul, Coordinator — un strat separat deasupra modulelor. Modulul VIPER (View-Interactor-Presenter-Entity-Router) include navigarea ca parte a modulului. Coordinator este extern față de module: creează și conectează modulele, dar nu face parte din ele. Coordinator este mai flexibil pentru reutilizarea ecranelor în diferite flow-uri.

MVVM-C — extinderea MVVM cu Coordinator. ViewModel nu cunoaște direct Coordinator — ViewController delegă navigarea prin ViewModel, ViewModel apelează coordonatorul printr-un protocol. MVVM-C — abordarea standard pentru proiectele iOS cu SwiftUI: Coordinator gestionează NavigationStack sau fullScreenCover, ViewModel apelează coordonatorul prin publicarea stării. Apple nu recomandă Coordinator pentru SwiftUI — NavigationStack și NavigationPath sunt mecanisme de navigare încorporate.

swift
// MVVM-C: ViewModel apelează Coordinator printr-un protocol
protocol AuthNavigationProtocol: AnyObject {
    func showMainScreen()
    func showForgotPassword()
}

class AuthViewModel: ObservableObject {
    weak var navigation: AuthNavigationProtocol?

    func loginTapped() {
        // logică...
        navigation?.showMainScreen()
    }
}

Întrebări frecvente

Este necesar Coordinator pentru SwiftUI?

Pentru SwiftUI, navigarea încorporată (NavigationStack, NavigationPath) înlocuiește adesea Coordinator. Apple recomandă navigarea bazată pe path. Coordinator are sens pentru flow-uri complexe cu condiții profunde (onboarding-autentificare-ecran principal în funcție de rol). Pentru aplicații simple pe SwiftUI, Coordinator este redundant — folosește NavigationPath.

Coordinator este același lucru cu Router?

Nu, sunt modele diferite. Coordinator gestionează flow-ul de navigare: decide ce ecran să afișeze, creează ViewController și le conectează. Router — abstractizare peste UINavigationController: push, present, pop, dismiss. Coordinator folosește Router pentru a executa navigarea. În unele implementări, Router include logica Coordinator (Router-per-screen), dar aceasta este o abatere de la modelul original.

Cum să eviți retain cycle în Coordinator?

Două locuri principale de scurgeri: childCoordinators (părintele ține copilul, uitând să-l elimine) și Router (UINavigationController ține ViewController). Soluție: elimină întotdeauna Coordinatorul copil din tablou la finish(). Folosește referință weak pentru delegate. Pentru Router — nu păstra o referință puternică la UINavigationController dacă este deja în ierarhia window. Testează deinit-ul Coordinatorului.

Când este Coordinator redundant?

Pentru aplicații cu 3-5 ecrane, Coordinator este redundant — segue sau navigationController.pushViewController simplu sunt mai ușoare. Pentru aplicații SwiftUI cu NavigationStack — de asemenea redundant. Coordinator este justificat pentru aplicații cu 15+ ecrane, flow-uri complexe (onboarding cu ramificații, autentificare cu recuperare parolă) și proiecte mixte UIKit/SwiftUI.

Cum să testezi Coordinator?

Mock Router — verificarea ce metode sunt apelate și cu ce parametri. Verificarea childCoordinators: după start() tabloul nu este gol, după finish() — gol. Coordinator se testează fără UI: Router este un protocol, mock-ul său nu necesită UIKit. Folosește XCTestExpectation pentru flow asincron. În Android, analog — testarea NavigationController și NavHost cu mock-navigare.

Rezumat

  • Coordinator — model de navigare care mută tranzițiile din ViewController într-o clasă separată
  • Router — abstractizare UINavigationController folosită de Coordinator
  • Ierarhie — Coordinatori părinți și copii cu delegarea rezultatelor
  • MVVM-C — abordare standard pentru proiectele UIKit cu Coordinator
  • SwiftUI — navigarea încorporată prin NavigationStack înlocuiește Coordinator
  • Testare — Coordinator se testează prin mock Router fără UIKit

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și