Coordinator: nyckelbegrepp, koordinatormönstret för iOS-navigering

Författare: IT Sectr Publicerad: 2026-02-18 Lästid: 9 min

Coordinator (koordinator) — ett arkitektoniskt navigeringsmönster som flyttar logiken för övergångar mellan skärmar från ViewController till separata klasser. Mönstret föreslogs av Soroush Khanlou 2015 och blev brett spridd inom iOS-communityt. Coordinator hanterar applikationens flöde: skapar och visar ViewController, överför data mellan skärmar och bearbetar slutförandet av flödet. Mönstret löser problemet med Massive View Controller genom att flytta ut navigeringen från kontrollern. Mer — i originalartikeln om Coordinator.

Huvudpunkter

  • Coordinator — navigeringsmönster som flyttar övergångslogik från ViewController
  • Ansvarsseparering — ViewController hanterar UI, Coordinator — navigering
  • Router — hjälpkomponent i Coordinator för abstraktion av UINavigationController
  • Flöde — sekvens av skärmar som hanteras av en Coordinator (t.ex. onboarding)
  • Delegering — barnkoordinatorer rapporterar till föräldern via delegate/protocol

Vad är Coordinator: essensen av navigeringsmönstret

Coordinator — ett mönster som tar över ansvaret för navigering i en iOS-applikation. I standard UIKit hanterar ViewController själv övergångarna: present, push, show segue — alla navigeringsmetoder anropas från UIViewController. Coordinator flyttar ut denna logik: ViewController rapporterar en händelse (t.ex. „användaren tryckte på inloggningsknappen“), Coordinator bestämmer vilken skärm som ska visas härnäst. ViewController blir bara kvar med UI-logik och delegerar navigeringen till koordinatorn.

Struktur för mönstret — CoordinatorProtocol med metoder start() och finish(). start() — början av flödet: skapa den första ViewController och visa den. finish() — slutförande av flödet med meddelande till föräldrakoordinatorn. Router — ett omslag runt UINavigationController (eller UISplitViewController) som tillhandahåller metoderna show, push, pop, dismiss. Koordinatorn arbetar inte direkt med UINavigationController — endast via Router. Detta gör det möjligt att testa navigering och byta UI-ramverk.

KomponentRollExempel
CoordinatorHantering av navigeringsflödeAuthCoordinator, ProfileCoordinator
RouterAbstraktion över UINavigationControllerpush, present, pop, dismiss
ViewControllerUI + delegering av händelser till CoordinatorLoginViewController.delegate

Problem som Coordinator löser — Massive View Controller (navigering — vanlig orsak till kontrollertillväxt). I standard UIKit innehåller ViewController prepareForSegue, navigeringsdelegater, bearbetning av unwind segues. Coordinator eliminerar detta. Segue i storyboard — en statisk koppling mellan skärmar, Coordinator ger dynamisk navigering med villkor. Testning av navigering blir möjlig: Coordinator kan testas utan UI genom att kontrollera ordningen för Router-anrop.

Coordinator i Swift: implementering med Router och Flow

Grundläggande Coordinator i Swift — ett protokoll med en associerad typ för Router och metoder start/finish. Router — ett protokoll som abstraherar UINavigationController. Den konkreta implementeringen av Router omsluter UINavigationController och delegerar metoder till den. Coordinator tar emot Router i init och använder den för navigering. Barnkoordinatorer lagras i arrayen childCoordinators för livscykelhantering.

swift
// Router — navigeringsabstraktion
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 — flödeshantering
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)
    }
}

Skapa Coordinator i AppDelegate/SceneDelegate — AppDelegate eller SceneDelegate skapar UINavigationController, omsluter den i NavigationRouter, skapar rot-Coordinator (AppCoordinator) och anropar start(). AppCoordinator bestämmer om onboarding, inloggning eller huvudskärm ska visas — beroende på applikationens tillstånd. Coordinator — den enda ingångspunkten för navigering, ViewController vet inte om andra skärmar.

Barn-Coordinator och hierarki av koordinatorer

Hierarki av koordinatorer — AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator. Barnkoordinatorn skapas av föräldern och lagras i arrayen childCoordinators. När barnkoordinatorn slutför sitt arbete anropar den finish() hos föräldern, och föräldern tar bort den från childCoordinators. Detta förhindrar minnesläckor: Coordinator har en stark referens till ViewController (via Router), och utan borttagning från childCoordinators frigörs inte objektet.

swift
// Delegat för kommunikation Coordinator -> Parent
protocol AuthCoordinatorDelegate: AnyObject {
    func authCoordinatorFinished(_ coordinator: AuthCoordinator)
}

class AuthCoordinator: CoordinatorProtocol {
    weak var delegate: AuthCoordinatorDelegate?

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

// AppCoordinator — förälder
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()
    }
}

Hantering av childCoordinators — borttagning av Coordinator från arrayen är det enda sättet att frigöra den. Om du glömmer att ta bort en slutförd Coordinator stannar den kvar i minnet tillsammans med ViewControllers. Rekommenderas: förälderns didMove(toParent:), callback vid slutförande eller Combine publisher för automatisk borttagning. Coordinator-mönstret specificerar inte meddelandemekanismen — delegate, closure eller Combine — valet är utvecklarens.

Sätt att överföra data mellan koordinatorer

Dataöverföring via delegat — barn-Coordinatorn definierar ett delegatprotokoll med metoder genom vilka resultaten överförs: func authCoordinator(_:didLoginWith user: User). Föräldern implementerar protokollet och tar emot data vid slutförandet av barnflödet. Detta är typsäkert och explicit. Nackdel: för varje barn-Coordinator måste ett separat protokoll skrivas. För projekt med 10+ Coordinatorer leder detta till en ökning av antalet filer.

Dataöverföring via Result-typ — metoden finish tar emot Result, där Output är den generiska typen av flödets resultat. Coordinator — en generik med en associerad resultattyp. start() med callback: start(completion: @escaping (Output) -> Void). Detta förkortar koden: inget separat protokoll behöver skrivas för varje koordinator. RxSwift/Combine: Coordinator publicerar resultatet via PassthroughSubject/Publisher. Valet beror på teamets arkitektoniska angreppssätt.

MetodFördelarNackdelar
DelegateTypsäkert, explicit, separata protokollMånga protokoll, mycket boilerplate
ClosureKompakt, färre filerSvårt att felsöka retain cycle
Combine/RxReaktivt, lätt att kombineraBeroende av bibliotek, svårare Debug

Delad datalager — Coordinatorer överför inte data direkt, utan använder en gemensam tjänst/databas. AuthCoordinator sparar token i Keychain/UserDefaults, ProfileCoordinator läser därifrån. Coordinatorer kommunicerar via gemensamt tillstånd (Dependency Injection-behållare), inte via direkta anrop. Detta minskar kopplingen mellan Coordinatorer, men skapar implicita beroenden av gemensamt tillstånd.

Jämförelse av Coordinator med Router, VIPER och MVVM-C

Coordinator vs Router — Router är en komponent i Coordinator som abstraherar UINavigationController. Coordinator ansvarar för flödet (vilken skärm som ska visas), Router — för mekaniken (hur den visas: push/present). Router är „hur“, Coordinator är „vad“. Router kan användas utan Coordinator (t.ex. Navigator-singleton), men Coordinator utan Router — bara en ViewController med en annan abstraktion. Vanligtvis används båda mönstren tillsammans.

Coordinator vs VIPER — VIPER har en komponent Wireframe som ansvarar för navigering — analog till Coordinator. I VIPER är Wireframe en del av modulen, Coordinator — ett separat lager ovanför modulerna. VIPER-modulen (View-Interactor-Presenter-Entity-Router) inkluderar navigering som en del av modulen. Coordinator är extern i förhållande till modulerna: den skapar och kopplar samman moduler, men ingår inte i deras sammansättning. Coordinator är mer flexibel för återanvändning av skärmar i olika flöden.

MVVM-C — utvidgning av MVVM med Coordinator. ViewModel känner inte direkt till Coordinator — ViewController delegerar navigering via ViewModel, ViewModel anropar koordinatorn via ett protokoll. MVVM-C — standardmetoden för iOS-projekt med SwiftUI: Coordinator hanterar NavigationStack eller fullScreenCover, ViewModel anropar koordinatorn genom att publicera tillstånd. Apple rekommenderar inte Coordinator för SwiftUI — NavigationStack och NavigationPath är inbyggda navigeringsmekanismer.

swift
// MVVM-C: ViewModel anropar Coordinator via protokoll
protocol AuthNavigationProtocol: AnyObject {
    func showMainScreen()
    func showForgotPassword()
}

class AuthViewModel: ObservableObject {
    weak var navigation: AuthNavigationProtocol?

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

Vanliga frågor

Behövs Coordinator för SwiftUI?

För SwiftUI ersätter inbyggd navigering (NavigationStack, NavigationPath) ofta Coordinator. Apple rekommenderar path-baserad navigering. Coordinator är meningsfullt för komplexa flöden med djupa villkor (onboarding-inloggning-huvudskärm beroende på roll). För enkla SwiftUI-applikationer är Coordinator överflödig — använd NavigationPath.

Är Coordinator samma sak som Router?

Nej, det är olika mönster. Coordinator hanterar navigeringsflödet: bestämmer vilken skärm som ska visas, skapar ViewController och kopplar samman dem. Router — abstraktion över UINavigationController: push, present, pop, dismiss. Coordinator använder Router för att utföra navigering. I vissa implementeringar inkluderar Router Coordinator-logik (Router-per-screen), men detta är en avvikelse från det ursprungliga mönstret.

Hur undviker man retain cycle i Coordinator?

Två huvudsakliga läckageplatser: childCoordinators (förälder håller barnet, glömmer att ta bort) och Router (UINavigationController håller ViewController). Lösning: ta alltid bort barn-Coordinatorn från arrayen vid finish(). Använd weak-referens för delegate. För Router — håll inte en stark referens till UINavigationController om den redan är i window-hierarkin. Testa Coordinatorns deinit.

När är Coordinator överflödig?

För applikationer med 3-5 skärmar är Coordinator överflödig — segue eller enkel navigationController.pushViewController är enklare. För SwiftUI-applikationer med NavigationStack — också överflödig. Coordinator är motiverad för applikationer med 15+ skärmar, komplexa flöden (onboarding med förgreningar, autentisering med lösenordsåterställning) och blandade UIKit/SwiftUI-projekt.

Hur testar man Coordinator?

Mock Router — kontrollera vilka metoder som anropas och med vilka parametrar. Kontroll av childCoordinators: efter start() är arrayen inte tom, efter finish() — tom. Coordinator testas utan UI: Router är ett protokoll, dess mock kräver inte UIKit. Använd XCTestExpectation för asynkront flöde. I Android analogt — testning av NavigationController och NavHost med mock-navigering.

Sammanfattning

  • Coordinator — navigeringsmönster som flyttar övergångar från ViewController till en separat klass
  • Router — abstraktion av UINavigationController som används av Coordinator
  • Hierarki — förälder- och barn-Coordinatorer med delegering av resultat
  • MVVM-C — standardmetod för UIKit-projekt med Coordinator
  • SwiftUI — inbyggd navigering via NavigationStack ersätter Coordinator
  • Testning — Coordinator testas via mock Router utan UIKit

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också