Coordinator (koordinatör) — ekranlar arasındaki geçiş mantığını ViewController'dan ayrı sınıflara taşıyan mimari bir gezinme modelidir. Model, 2015 yılında Soroush Khanlou tarafından önerilmiş ve iOS topluluğunda geniş çapta benimsenmiştir. Coordinator, uygulama akışını yönetir: ViewController'ı oluşturur ve görüntüler, ekranlar arasında veri aktarır ve akış tamamlanmasını işler. Model, denetleyiciden gezinmeyi çıkararak Massive View Controller sorununu çözer. Daha fazla bilgi için Coordinator hakkındaki orijinal makale'ye bakın.
Önemli Noktalar
Coordinator — bir iOS uygulamasında gezinme sorumluluğunu üstlenen bir modeldir. Standart UIKit'te, ViewController'ın kendisi geçişleri yönetir: present, push, show segue — tüm gezinme yöntemleri UIViewController'dan çağrılır. Coordinator bu mantığı çıkarır: ViewController bir olay bildirir (örneğin, «kullanıcı giriş düğmesine tıkladı»), Coordinator bir sonraki ekranın ne olacağına karar verir. ViewController yalnızca UI mantığıyla kalır ve gezinmeyi koordinatöre devreder.
Model yapısı — start() ve finish() yöntemleriyle CoordinatorProtocol. start() — bir akışın başlangıcı: ilk ViewController'ı oluşturma ve görüntüleme. finish() — üst koordinatöre bildirimle akışın tamamlanması. Router — show, push, pop, dismiss yöntemlerini sağlayan, UINavigationController (veya UISplitViewController) etrafında bir sarmalayıcı. Koordinatör doğrudan UINavigationController ile çalışmaz — yalnızca Router aracılığıyla. Bu, gezinmeyi test etmeyi ve UI framework'ünü değiştirmeyi sağlar.
| Bileşen | Rol | Örnek |
|---|---|---|
| Coordinator | Gezinme akışı yönetimi | AuthCoordinator, ProfileCoordinator |
| Router | UINavigationController üzerinde soyutlama | push, present, pop, dismiss |
| ViewController | UI + Coordinator'a olay devri | LoginViewController.delegate |
Coordinator'ın çözdüğü sorunlar — Massive View Controller (gezinme, denetleyicinin şişmesinin yaygın bir nedenidir). Standart UIKit'te ViewController, prepareForSegue, gezinme delegeleri, unwind segues işleme içerir. Coordinator bunu ortadan kaldırır. Storyboard'daki Segues, ekranlar arasında statik bağlantılardır, Coordinator koşullarla dinamik gezinme sağlar. Gezinmeyi test etmek mümkün hale gelir: Router çağrılarının sırasını doğrulayarak Coordinator UI olmadan test edilebilir.
Swift'te temel Coordinator — Router için ilişkili bir tür ve start/finish yöntemleriyle bir protokol. Router — UINavigationController'ı soyutlayan bir protokol. Router'ın somut bir uygulaması, UINavigationController'ı sarar ve yöntemleri ona devreder. Coordinator, init'te Router'ı kabul eder ve gezinme için kullanır. Alt koordinatörler, yaşam döngüsü yönetimi için childCoordinators dizisinde saklanır.
// Router — gezinme soyutlaması
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 — akış yönetimi
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'te Coordinator oluşturma — AppDelegate veya SceneDelegate, UINavigationController'ı oluşturur, NavigationRouter'a sarar, bir kök Coordinator (AppCoordinator) oluşturur ve start()'ı çağırır. AppCoordinator, uygulama durumuna bağlı olarak onboarding, giriş veya ana ekranı gösterip göstermemeye karar verir. Coordinator, gezinme için tek giriş noktasıdır, ViewController diğer ekranları bilmez.
Koordinatör hiyerarşisi — AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator. Alt koordinatör, üst öğe tarafından oluşturulur ve childCoordinators dizisinde saklanır. Alt koordinatör işini tamamladığında, üst öğede finish()'i çağırır ve üst öğe onu childCoordinators'tan kaldırır. Bu, bellek sızıntılarını önler: Coordinator'ın ViewController'a (Router aracılığıyla) güçlü bir referansı vardır ve childCoordinators'tan kaldırılmazsa nesne serbest bırakılmaz.
// Coordinator -> Parent iletişimi için Delegate
protocol AuthCoordinatorDelegate: AnyObject {
func authCoordinatorFinished(_ coordinator: AuthCoordinator)
}
class AuthCoordinator: CoordinatorProtocol {
weak var delegate: AuthCoordinatorDelegate?
func finish() {
delegate?.authCoordinatorFinished(self)
}
}
// AppCoordinator — üst
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 yönetimi — Coordinator'ı diziden kaldırmak, onu serbest bırakmanın tek yoludur. Tamamlanmış bir Coordinator'ı kaldırmayı unutursanız, ViewController'larıyla birlikte bellekte kalır. Önerilen yaklaşımlar: üst öğenin didMove(toParent:), tamamlanmada callback veya otomatik kaldırma için Combine publisher. Coordinator modeli bildirim mekanizmasını belirtmez — delegate, closure veya Combine — seçim geliştiriciye kalmıştır.
Delegate aracılığıyla veri aktarma — alt Coordinator, sonuçların iletildiği yöntemlerle bir delegate protokolü tanımlar: func authCoordinator(_:didLoginWith user: User). Üst öğe protokolü uygular ve alt akış tamamlandığında verileri alır. Bu tür güvenlidir ve açıktır. Dezavantaj: her alt Coordinator için ayrı bir protokol gerekir. 10+ Coordinator'ı olan projelerde bu, dosya sayısında artışa yol açar.
Result türü aracılığıyla veri aktarma — finish yöntemi, Output'un akış sonucunun genel türü olduğu Result
| Yöntem | Avantajlar | Dezavantajlar |
|---|---|---|
| Delegate | Tür güvenli, açık, ayrı protokoller | Çok protokol, çok sayıda tekrar eden kod |
| Closure | Kompakt, daha az dosya | retain cycle'ı hata ayıklamak zor |
| Combine/Rx | Tepkisel, birleştirmesi kolay | Kütüphane bağımlılığı, hata ayıklaması zor |
Paylaşılan veri katmanı — Coordinator'lar verileri doğrudan aktarmaz, bunun yerine paylaşılan bir hizmet/depo kullanır. AuthCoordinator, belirteci Keychain/UserDefaults'a kaydeder, ProfileCoordinator oradan okur. Coordinator'lar doğrudan çağrılar yerine paylaşılan durum (Dependency Injection kapsayıcısı) aracılığıyla iletişim kurar. Bu, Coordinator'lar arasındaki bağlılığı azaltır ancak paylaşılan duruma örtük bağımlılıklar oluşturur.
Coordinator vs Router — Router, UINavigationController'ı soyutlayan Coordinator'ın bir bileşenidir. Coordinator akıştan (hangi ekranın gösterileceği), Router ise mekanikten (nasıl gösterileceği: push/present) sorumludur. Router «nasıl», Coordinator «ne»dir. Router, Coordinator olmadan kullanılabilir (ör. Navigator singleton), ancak Coordinator Router olmadan yalnızca farklı bir soyutlamaya sahip bir ViewController'dır. Genellikle her iki model birlikte kullanılır.
Coordinator vs VIPER — VIPER'da gezinmeden sorumlu bir Wireframe bileşeni vardır — Coordinator'a benzer. VIPER'da Wireframe modülün bir parçasıdır, Coordinator modüllerin üzerinde ayrı bir katmandır. Bir VIPER modülü (View-Interactor-Presenter-Entity-Router), gezinmeyi modülün bir parçası olarak içerir. Coordinator modüllere göre dışsaldır: modülleri oluşturur ve bağlar, ancak onların bir parçası değildir. Coordinator, ekranların farklı akışlarda yeniden kullanımı için daha esnektir.
MVVM-C — Coordinator ile MVVM'nin bir uzantısı. ViewModel, Coordinator'ı doğrudan bilmez — ViewController, ViewModel aracılığıyla gezinmeyi devreder, ViewModel bir protokol aracılığıyla coordinator'ı çağırır. MVVM-C, SwiftUI ile iOS projeleri için standart yaklaşımdır: Coordinator, NavigationStack veya fullScreenCover'ı yönetir, ViewModel durum yayınlayarak coordinator'ı çağırır. Apple, SwiftUI için Coordinator'ı önermez — NavigationStack ve NavigationPath yerleşik gezinme mekanizmalarıdır.
// MVVM-C: ViewModel protokol aracılığıyla Coordinator'ı çağırır
protocol AuthNavigationProtocol: AnyObject {
func showMainScreen()
func showForgotPassword()
}
class AuthViewModel: ObservableObject {
weak var navigation: AuthNavigationProtocol?
func loginTapped() {
// mantık...
navigation?.showMainScreen()
}
}
Sıkça Sorulan Sorular
SwiftUI için yerleşik gezinme (NavigationStack, NavigationPath) genellikle Coordinator'ın yerini alır. Apple, path tabanlı gezinmeyi önerir. Coordinator, derin koşulları olan karmaşık akışlar için (role bağlı olarak onboarding-giriş-ana ekran) anlamlıdır. Basit SwiftUI uygulamaları için Coordinator gereksizdir — NavigationPath kullanın.
Hayır, bunlar farklı modellerdir. Coordinator gezinme akışını yönetir: hangi ekranın gösterileceğine karar verir, ViewController'ları oluşturur ve bağlar. Router, UINavigationController üzerinde bir soyutlamadır: push, present, pop, dismiss. Coordinator, gezinmeyi gerçekleştirmek için Router'ı kullanır. Bazı uygulamalarda Router, Coordinator mantığını içerir (Router-per-screen), ancak bu orijinal modelden sapmadır.
İki ana sızıntı kaynağı: childCoordinators (üst öğe alt öğeyi tutar, kaldırmayı unutur) ve Router (UINavigationController ViewController'ı tutar). Çözüm: finish()'te alt Coordinator'ı her zaman diziden kaldırın. Delegate için weak referans kullanın. Router için — UINavigationController zaten window hiyerarşisindeyse güçlü referans tutmayın. Coordinator'ın deinit'ini test edin.
3-5 ekranlı uygulamalar için Coordinator gereksizdir — segue veya basit navigationController.pushViewController daha kolaydır. NavigationStack ile SwiftUI uygulamaları için de gereksizdir. Coordinator, 15+ ekranlı, karmaşık akışlı (dallanmalı onboarding, şifre kurtarmalı yetkilendirme) ve karma UIKit/SwiftUI projeleri için uygundur.
Mock Router — hangi yöntemlerin hangi parametrelerle çağrıldığını kontrol edin. childCoordinators'ı kontrol edin: start()'tan sonra dizi boş değil, finish()'ten sonra — boş. Coordinator UI olmadan test edilir: Router bir protokoldür, mock'u UIKit gerektirmez. Async akış için XCTestExpectation kullanın. Android'de — mock gezinme ile NavigationController ve NavHost'un benzer testi.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun