Coordinator (koordinator) — naviqasiyanın arxitektura nümunəsidir, ekranlar arasında keçid məntiqini ViewController-dan ayrı siniflərə çıxarır. Nümunə 2015-ci ildə Soroush Khanlou tərəfindən təklif edilmiş və iOS icmasında geniş yayılmışdır. Coordinator tətbiqin flow-unu idarə edir: ViewController yaradır və göstərir, ekranlar arasında məlumat ötürür və flow-un tamamlanmasını emal edir. Nümunə Massive View Controller problemini həll edir, naviqasiyanı kontrollerdən çıxarır. Ətraflı — Coordinator orijinal məqaləsində.
Əsas məqamlar
Coordinator — iOS tətbiqində naviqasiya üçün məsuliyyəti öz üzərinə götürən nümunədir. Standart UIKit-də ViewController keçidləri özü idarə edir: present, push, show segue — bütün naviqasiya metodları UIViewController-dən çağrılır. Coordinator bu məntiqi çıxarır: ViewController hadisə barədə məlumat verir (məsələn, „istifadəçi daxilolma düyməsini basdı“), Coordinator növbəti hansı ekranı göstərəcəyinə qərar verir. ViewController yalnız UI məntiqi ilə qalır və naviqasiyanı koordinatora həvalə edir.
Nümunənin strukturu — start() və finish() metodları ilə CoordinatorProtocol. start() — flow-un başlanğıcı: ilk ViewController-ün yaradılması və göstərilməsi. finish() — valideyn koordinatora bildirişlə flow-un tamamlanması. Router — UINavigationController (və ya UISplitViewController) üzərində qabıq, show, push, pop, dismiss metodlarını təqdim edir. Koordinator birbaşa UINavigationController ilə işləmir — yalnız Router vasitəsilə. Bu, naviqasiyanı test etməyə və UI freymvorkunu dəyişməyə imkan verir.
| Komponent | Rol | Nümunə |
|---|---|---|
| Coordinator | Naviqasiya flow-unun idarə edilməsi | AuthCoordinator, ProfileCoordinator |
| Router | UINavigationController üzərində abstraksiya | push, present, pop, dismiss |
| ViewController | UI + hadisələrin Coordinator-a delegasiyası | LoginViewController.delegate |
Coordinator-un həll etdiyi problemlər — Massive View Controller (naviqasiya — kontrollerin böyüməsinin ümumi səbəbi). Standart UIKit-də ViewController prepareForSegue, naviqasiya delegate-ləri, unwind segues emalını ehtiva edir. Coordinator bunu aradan qaldırır. Storyboard-da Segue — ekranlar arasında statik əlaqə, Coordinator şərtlərlə dinamik naviqasiya təmin edir. Naviqasiyanın test edilməsi mümkün olur: Coordinator-u UI olmadan, Router çağırışlarının ardıcıllığını yoxlayaraq test etmək olar.
Swift-də əsas Coordinator — Router üçün əlaqəli tip və start/finish metodları ilə protokol. Router — UINavigationController-i abstraksiya edən protokol. Router-in konkret tətbiqi UINavigationController-i qabıqlayır və metodları ona həvalə edir. Coordinator Router-i init-də qəbul edir və naviqasiya üçün istifadə edir. Uşaq koordinatorları həyat dövrünü idarə etmək üçün childCoordinators massivində saxlanılır.
// Router — naviqasiya abstraksiyası
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-un idarə edilməsi
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)
}
}
AppDelegate/SceneDelegate-də Coordinator-un yaradılması — AppDelegate və ya SceneDelegate UINavigationController yaradır, onu NavigationRouter-ə qabıqlayır, kök Coordinator (AppCoordinator) yaradır və start() çağırır. AppCoordinator tətbiqin vəziyyətindən asılı olaraq onboarding, daxilolma və ya əsas ekranı göstərməyə qərar verir. Coordinator — naviqasiya üçün yeganə giriş nöqtəsi, ViewController digər ekranlar haqqında bilmir.
Koordinatorlar iyerarxiyası — AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator. Uşaq koordinator valideyn tərəfindən yaradılır və childCoordinators massivində saxlanılır. Uşaq koordinator işini bitirdikdə, valideyndə finish() çağırır və valideyn onu childCoordinators-dan silir. Bu, yaddaş sızmalarının qarşısını alır: Coordinator ViewController-a (Router vasitəsilə) güclü istinada malikdir və childCoordinators-dan silinmədən obyekt azad olunmayacaq.
// Coordinator -> Parent əlaqəsi üçün Delegate
protocol AuthCoordinatorDelegate: AnyObject {
func authCoordinatorFinished(_ coordinator: AuthCoordinator)
}
class AuthCoordinator: CoordinatorProtocol {
weak var delegate: AuthCoordinatorDelegate?
func finish() {
delegate?.authCoordinatorFinished(self)
}
}
// AppCoordinator — valideyn
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-un idarə edilməsi — Coordinator-un massivdən silinməsi onu azad etməyin yeganə yoludur. Tamamlanmış Coordinator-u silməyi unutsanız, o, ViewController-larla birlikdə yaddaşda qalır. Tövsiyə olunur: valideynin didMove(toParent:), tamamlanma callback-i və ya avtomatik silmə üçün Combine publisher. Coordinator nümunəsi bildiriş mexanizmini spesifikasiya etmir — delegate, closure və ya Combine — seçim tərtibatçınındır.
Delegate vasitəsilə məlumat ötürülməsi — uşaq Coordinator nəticələrin ötürüldüyü metodlarla delegate protokolunu təyin edir: func authCoordinator(_:didLoginWith user: User). Valideyn protokolu tətbiq edir və uşaq flow-un tamamlanmasında məlumat alır. Bu tip təhlükəsiz və aşkardır. Çatışmazlıq: hər uşaq Coordinator üçün ayrıca protokol yazmaq lazımdır. 10+ Coordinator olan layihələr üçün bu, faylların sayının artmasına səbəb olur.
Result tipi vasitəsilə məlumat ötürülməsi — finish metodu Result
| Metod | Üstünlüklər | Çatışmazlıqlar |
|---|---|---|
| Delegate | Tip təhlükəsiz, aşkar, ayrı protokollar | Çox protokol, çox boilerplate |
| Closure | Kompakt, az fayl | Retain cycle-i debug etmək çətindir |
| Combine/Rx | Reaktiv, asan birləşdirmək | Kitabxanadan asılılıq, çətin Debug |
Shared data layer — Coordinatory birbaşa məlumat ötürmür, ortaq xidmət/repozitoridən istifadə edir. AuthCoordinator token-i Keychain/UserDefaults-da saxlayır, ProfileCoordinator oradan oxuyur. Coordinatory ortaq vəziyyət (Dependency Injection container) vasitəsilə ünsiyyət qurur, birbaşa çağırışlarla deyil. Bu, Coordinatorların bağlılığını azaldır, lakin ortaq vəziyyətdən gizli asılılıqlar yaradır.
Coordinator vs Router — Router UINavigationController-i abstraksiya edən Coordinator komponentidir. Coordinator flow-a cavabdehdir (hansı ekranı göstərmək), Router — mexanikaya (necə göstərmək: push/present). Router „necə“, Coordinator „nə“dir. Router-i Coordinator olmadan istifadə etmək olar (məsələn, Navigator-sinqleton), lakin Coordinator Router olmadan — sadəcə fərqli abstraksiya ilə ViewController. Adətən hər iki nümunə birlikdə istifadə olunur.
Coordinator vs VIPER — VIPER naviqasiya üçün Coordinator-un analoqu olan Wireframe komponentinə malikdir. VIPER-də Wireframe modulun bir hissəsidir, Coordinator — modullar üzərində ayrıca təbəqə. VIPER modulu (View-Interactor-Presenter-Entity-Router) naviqasiyanı modulun bir hissəsi kimi daxil edir. Coordinator modullara nisbətən xarici: modulları yaradır və birləşdirir, lakin onların tərkibinə daxil deyil. Coordinator ekranların müxtəlif flow-larda təkrar istifadəsi üçün daha çevikdir.
MVVM-C — MVVM-nin Coordinator ilə genişləndirilməsi. ViewModel birbaşa Coordinator-u tanımır — ViewController naviqasiyanı ViewModel vasitəsilə həvalə edir, ViewModel protokol vasitəsilə koordinatoru çağırır. MVVM-C — SwiftUI ilə iOS layihələri üçün standart yanaşma: Coordinator NavigationStack və ya fullScreenCover-i idarə edir, ViewModel vəziyyəti dərc edərək koordinatoru çağırır. Apple SwiftUI üçün Coordinator-u tövsiyə etmir — NavigationStack və NavigationPath daxili naviqasiya mexanizmləridir.
// MVVM-C: ViewModel protokol vasitəsilə Coordinator-u çağırır
protocol AuthNavigationProtocol: AnyObject {
func showMainScreen()
func showForgotPassword()
}
class AuthViewModel: ObservableObject {
weak var navigation: AuthNavigationProtocol?
func loginTapped() {
// məntiq...
navigation?.showMainScreen()
}
}
Tez-tez verilən suallar
SwiftUI üçün daxili naviqasiya (NavigationStack, NavigationPath) tez-tez Coordinator-u əvəz edir. Apple path-based naviqasiyanı tövsiyə edir. Coordinator dərin şərtləri olan mürəkkəb flow-lar üçün məna kəsb edir (roldan asılı olaraq onboarding-daxilolma-əsas ekran). Sadə SwiftUI tətbiqləri üçün Coordinator artıqdır — NavigationPath istifadə edin.
Xeyr, bunlar fərqli nümunələrdir. Coordinator naviqasiya flow-unu idarə edir: hansı ekranı göstərəcəyinə qərar verir, ViewController yaradır və onları birləşdirir. Router — UINavigationController üzərində abstraksiya: push, present, pop, dismiss. Coordinator naviqasiyanı yerinə yetirmək üçün Router-dən istifadə edir. Bəzi tətbiqlərdə Router Coordinator məntiqini ehtiva edir (Router-per-screen), lakin bu orijinal nümunədən sapmadır.
İki əsas sızma yeri: childCoordinators (valideyn uşağı saxlayır, silməyi unudur) və Router (UINavigationController ViewController-i saxlayır). Həlli: həmişə uşaq Coordinator-u finish() zamanı massivdən silmək. Delegate üçün weak istinaddan istifadə edin. Router üçün — UINavigationController-a güclü istinad saxlamayın, əgər o artıq window iyerarxiyasındadırsa. Coordinator-un deinit-ini test edin.
3-5 ekranlı tətbiqlər üçün Coordinator artıqdır — segue və ya sadə navigationController.pushViewController daha sadədir. NavigationStack ilə SwiftUI tətbiqləri üçün də artıqdır. Coordinator 15+ ekranlı, mürəkkəb flow-lu (şaxələnmiş onboarding, parol bərpası ilə autorizasiya) və qarışıq UIKit/SwiftUI layihələri üçün əsaslandırılmışdır.
Mock Router — hansı metodların hansı parametrlərlə çağrıldığını yoxlamaq. childCoordinators-un yoxlanması: start() sonra massiv boş deyil, finish() sonra — boş. Coordinator UI olmadan test edilir: Router protokoldur, onun mock-u UIKit tələb etmir. Async flow üçün XCTestExpectation istifadə edin. Android-də analoji — NavigationController və NavHost-un mock-naviqasiya ilə test edilməsi.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun