Coordinator: konsep kunci, pola koordinator untuk navigasi iOS

Penulis: IT Sectr Diterbitkan: 2026-02-18 Waktu membaca: 9 mnt

Coordinator (koordinator) — pola arsitektur navigasi yang memindahkan logika transisi antar layar dari ViewController ke kelas terpisah. Pola ini diusulkan oleh Soroush Khanlou pada tahun 2015 dan mendapatkan popularitas luas di komunitas iOS. Coordinator mengelola flow aplikasi: membuat dan menampilkan ViewController, mentransfer data antar layar, dan memproses penyelesaian flow. Pola ini memecahkan masalah Massive View Controller dengan memindahkan navigasi dari controller. Selengkapnya — di artikel asli Coordinator.

Poin utama

  • Coordinator — pola navigasi yang memindahkan logika transisi dari ViewController
  • Pemisahan tanggung jawab — ViewController mengelola UI, Coordinator — navigasi
  • Router — komponen pembantu Coordinator untuk abstraksi UINavigationController
  • Flow — urutan layar yang dikelola oleh satu Coordinator (misalnya, onboarding)
  • Delegasi — koordinator anak melaporkan ke induk melalui delegate/protocol

Apa itu Coordinator: esensi pola navigasi

Coordinator — pola yang mengambil alih tanggung jawab navigasi di aplikasi iOS. Di UIKit standar, ViewController sendiri mengelola transisi: present, push, show segue — semua metode navigasi dipanggil dari UIViewController. Coordinator memindahkan logika ini: ViewController melaporkan peristiwa (misalnya, „pengguna menekan tombol login“), Coordinator memutuskan layar mana yang akan ditampilkan selanjutnya. ViewController hanya tinggal dengan logika UI dan mendelegasikan navigasi ke koordinator.

Struktur pola — CoordinatorProtocol dengan metode start() dan finish(). start() — awal flow: membuat ViewController pertama dan menampilkannya. finish() — penyelesaian flow dengan pemberitahuan ke koordinator induk. Router — pembungkus di atas UINavigationController (atau UISplitViewController) yang menyediakan metode show, push, pop, dismiss. Koordinator tidak bekerja langsung dengan UINavigationController — hanya melalui Router. Ini memungkinkan pengujian navigasi dan pergantian framework UI.

KomponenPeranContoh
CoordinatorMengelola flow navigasiAuthCoordinator, ProfileCoordinator
RouterAbstraksi di atas UINavigationControllerpush, present, pop, dismiss
ViewControllerUI + delegasi peristiwa ke CoordinatorLoginViewController.delegate

Masalah yang dipecahkan Coordinator — Massive View Controller (navigasi — penyebab umum membesarnya controller). Di UIKit standar, ViewController berisi prepareForSegue, delegasi navigasi, pemrosesan unwind segues. Coordinator menghilangkan ini. Segue di storyboard — hubungan statis antar layar, Coordinator memberikan navigasi dinamis dengan kondisi. Pengujian navigasi menjadi mungkin: Coordinator dapat diuji tanpa UI dengan memeriksa urutan panggilan Router.

Coordinator di Swift: implementasi dengan Router dan Flow

Coordinator dasar di Swift — protokol dengan tipe terkait untuk Router dan metode start/finish. Router — protokol yang mengabstraksi UINavigationController. Implementasi konkret Router membungkus UINavigationController dan mendelegasikan metode kepadanya. Coordinator menerima Router di init dan menggunakannya untuk navigasi. Koordinator anak disimpan dalam array childCoordinators untuk manajemen siklus hidup.

swift
// Router — abstraksi navigasi
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 — manajemen 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)
    }
}

Membuat Coordinator di AppDelegate/SceneDelegate — AppDelegate atau SceneDelegate membuat UINavigationController, membungkusnya dalam NavigationRouter, membuat Coordinator akar (AppCoordinator) dan memanggil start(). AppCoordinator memutuskan untuk menampilkan onboarding, login, atau layar utama — tergantung status aplikasi. Coordinator — satu-satunya titik masuk untuk navigasi, ViewController tidak tahu tentang layar lain.

Coordinator Anak dan hierarki koordinator

Hierarki koordinator — AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator. Koordinator anak dibuat oleh induk dan disimpan dalam array childCoordinators. Ketika koordinator anak menyelesaikan tugasnya, ia memanggil finish() pada induk, dan induk menghapusnya dari childCoordinators. Ini mencegah kebocoran memori: Coordinator memiliki referensi kuat ke ViewController (melalui Router), dan tanpa penghapusan dari childCoordinators, objek tidak akan dibebaskan.

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

class AuthCoordinator: CoordinatorProtocol {
    weak var delegate: AuthCoordinatorDelegate?

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

// AppCoordinator — induk
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()
    }
}

Mengelola childCoordinators — penghapusan Coordinator dari array adalah satu-satunya cara untuk membebaskannya. Jika Anda lupa menghapus Coordinator yang selesai, ia tetap di memori bersama dengan ViewController. Direkomendasikan: didMove(toParent:) dari induk, callback saat selesai, atau publisher Combine untuk penghapusan otomatis. Pola Coordinator tidak menentukan mekanisme pemberitahuan — delegate, closure, atau Combine — pilihan ada pada pengembang.

Cara transfer data antar koordinator

Transfer data melalui delegat — Coordinator anak mendefinisikan protokol delegat dengan metode yang digunakan untuk mentransfer hasil: func authCoordinator(_:didLoginWith user: User). Induk mengimplementasikan protokol dan menerima data saat penyelesaian flow anak. Ini aman tipe dan eksplisit. Kekurangan: untuk setiap Coordinator anak perlu menulis protokol terpisah. Untuk proyek dengan 10+ Coordinator, ini menyebabkan peningkatan jumlah file.

Transfer data melalui tipe Result — metode finish menerima Result, di mana Output adalah tipe generik hasil flow. Coordinator — generik dengan tipe hasil terkait. start() dengan callback: start(completion: @escaping (Output) -> Void). Ini mempersingkat kode: tidak perlu menulis protokol terpisah untuk setiap koordinator. RxSwift/Combine: Coordinator mempublikasikan hasil melalui PassthroughSubject/Publisher. Pilihan tergantung pada pendekatan arsitektur tim.

MetodeKelebihanKekurangan
DelegateAman tipe, eksplisit, protokol terpisahBanyak protokol, banyak boilerplate
ClosureRingkas, lebih sedikit fileSulit debug retain cycle
Combine/RxReaktif, mudah dikombinasikanKetergantungan pada pustaka, Debug lebih sulit

Lapisan data bersama — Coordinator tidak mentransfer data secara langsung, tetapi menggunakan layanan/repositori bersama. AuthCoordinator menyimpan token di Keychain/UserDefaults, ProfileCoordinator membaca dari sana. Coordinator berkomunikasi melalui keadaan bersama (kontainer Dependency Injection), bukan melalui panggilan langsung. Ini mengurangi keterikatan Coordinator, tetapi menciptakan ketergantungan implisit pada keadaan bersama.

Perbandingan Coordinator dengan Router, VIPER dan MVVM-C

Coordinator vs Router — Router adalah komponen Coordinator yang mengabstraksi UINavigationController. Coordinator bertanggung jawab atas flow (layar mana yang ditampilkan), Router — atas mekanika (bagaimana menampilkan: push/present). Router adalah „bagaimana“, Coordinator adalah „apa“. Router dapat digunakan tanpa Coordinator (misalnya, Navigator-singleton), tetapi Coordinator tanpa Router — hanya ViewController dengan abstraksi lain. Biasanya kedua pola digunakan bersama.

Coordinator vs VIPER — VIPER memiliki komponen Wireframe yang bertanggung jawab atas navigasi — analog Coordinator. Di VIPER, Wireframe adalah bagian dari modul, Coordinator — lapisan terpisah di atas modul. Modul VIPER (View-Interactor-Presenter-Entity-Router) mencakup navigasi sebagai bagian dari modul. Coordinator bersifat eksternal terhadap modul: ia membuat dan menghubungkan modul, tetapi tidak termasuk dalam komposisinya. Coordinator lebih fleksibel untuk penggunaan ulang layar di flow yang berbeda.

MVVM-C — perluasan MVVM dengan Coordinator. ViewModel tidak tahu langsung tentang Coordinator — ViewController mendelegasikan navigasi melalui ViewModel, ViewModel memanggil koordinator melalui protokol. MVVM-C — pendekatan standar untuk proyek iOS dengan SwiftUI: Coordinator mengelola NavigationStack atau fullScreenCover, ViewModel memanggil koordinator dengan mempublikasikan status. Apple tidak merekomendasikan Coordinator untuk SwiftUI — NavigationStack dan NavigationPath adalah mekanisme navigasi bawaan.

swift
// MVVM-C: ViewModel memanggil Coordinator melalui protokol
protocol AuthNavigationProtocol: AnyObject {
    func showMainScreen()
    func showForgotPassword()
}

class AuthViewModel: ObservableObject {
    weak var navigation: AuthNavigationProtocol?

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

Pertanyaan yang sering diajukan

Apakah Coordinator diperlukan untuk SwiftUI?

Untuk SwiftUI, navigasi bawaan (NavigationStack, NavigationPath) sering menggantikan Coordinator. Apple merekomendasikan navigasi berbasis path. Coordinator berguna untuk flow kompleks dengan kondisi mendalam (onboarding-login-layar utama tergantung peran). Untuk aplikasi SwiftUI sederhana, Coordinator berlebihan — gunakan NavigationPath.

Apakah Coordinator itu Router?

Tidak, ini pola yang berbeda. Coordinator mengelola flow navigasi: memutuskan layar mana yang akan ditampilkan, membuat ViewController dan menghubungkannya. Router — abstraksi di atas UINavigationController: push, present, pop, dismiss. Coordinator menggunakan Router untuk menjalankan navigasi. Dalam beberapa implementasi, Router menyertakan logika Coordinator (Router-per-screen), tetapi ini penyimpangan dari pola asli.

Bagaimana menghindari retain cycle di Coordinator?

Dua tempat utama kebocoran: childCoordinators (induk menyimpan anak, lupa menghapus) dan Router (UINavigationController menyimpan ViewController). Solusi: selalu hapus Coordinator anak dari array saat finish(). Gunakan referensi weak untuk delegate. Untuk Router — jangan menyimpan referensi kuat ke UINavigationController jika sudah dalam hierarki window. Uji deinit Coordinator.

Kapan Coordinator berlebihan?

Untuk aplikasi dengan 3-5 layar, Coordinator berlebihan — segue atau navigationController.pushViewController sederhana lebih mudah. Untuk aplikasi SwiftUI dengan NavigationStack — juga berlebihan. Coordinator dibenarkan untuk aplikasi dengan 15+ layar, flow kompleks (onboarding dengan percabangan, otorisasi dengan pemulihan kata sandi) dan proyek campuran UIKit/SwiftUI.

Bagaimana menguji Coordinator?

Mock Router — memeriksa metode mana yang dipanggil dan dengan parameter apa. Pemeriksaan childCoordinators: setelah start() array tidak kosong, setelah finish() — kosong. Coordinator diuji tanpa UI: Router adalah protokol, mock-nya tidak memerlukan UIKit. Gunakan XCTestExpectation untuk flow asinkron. Di Android analog — menguji NavigationController dan NavHost dengan mock-navigasi.

Kesimpulan

  • Coordinator — pola navigasi yang memindahkan transisi dari ViewController ke kelas terpisah
  • Router — abstraksi UINavigationController yang digunakan oleh Coordinator
  • Hierarki — Coordinator induk dan anak dengan delegasi hasil
  • MVVM-C — pendekatan standar untuk proyek UIKit dengan Coordinator
  • SwiftUI — navigasi bawaan melalui NavigationStack menggantikan Coordinator
  • Pengujian — Coordinator diuji melalui mock Router tanpa UIKit

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga