Coordinator (koordineytor) — isang arkitektural na pattern ng nabigasyon na naglilipat ng lohika ng mga transisyon sa pagitan ng mga screen mula sa ViewController patungo sa magkakahiwalay na mga klase. Ang pattern ay iminungkahi ni Soroush Khanlou noong 2015 at naging malawak na ginagamit sa komunidad ng iOS. Coordinator ang namamahala sa flow ng aplikasyon: lumilikha at nagpapakita ng ViewController, naglilipat ng data sa pagitan ng mga screen at nagpoproseso ng pagkumpleto ng flow. Nalulutas ng pattern ang problema ng Massive View Controller sa pamamagitan ng paglalabas ng nabigasyon mula sa controller. Higit pa — sa orihinal na artikulo ng Coordinator.
Mga pangunahing punto
Coordinator — isang pattern na pumapalit ng responsibilidad para sa nabigasyon sa isang iOS na aplikasyon. Sa karaniwang UIKit, ang ViewController mismo ang namamahala ng mga transisyon: present, push, show segue — lahat ng paraan ng nabigasyon ay tinatawag mula sa UIViewController. Coordinator ang naglilipat ng lohika na ito: ang ViewController ay nag-uulat ng isang pangyayari (hal., „pinindot ng gumagamit ang button ng pag-login“), Coordinator ang nagpapasya kung aling screen ang susunod na ipapakita. Ang Viewcontroller ay nananatili lamang sa UI lohika at itinatagubilin ang nabigasyon sa koordineytor.
Istruktura ng pattern — CoordinatorProtocol na may mga paraang start() at finish(). start() — simula ng flow: paglikha ng unang ViewController at pagpapakita. finish() — pagkumpleto ng flow na may abiso sa magulang na koordineytor. Router — isang pambalot sa UINavigationController (o UISplitViewController) na nagbibigay ng mga paraang show, push, pop, dismiss. Ang koordineytor ay hindi direktang gumagawa sa UINavigationController — sa pamamagitan lamang ng Router. Ito ay nagpapahintulot sa pag-test ng nabigasyon at pagpapalit ng UI framework.
| Bahagi | Papel | Halimbawa |
|---|---|---|
| Coordinator | Pamamahala ng flow ng nabigasyon | AuthCoordinator, ProfileCoordinator |
| Router | Abstraksyon sa UINavigationController | push, present, pop, dismiss |
| ViewController | UI + delegasyon ng mga pangyayari sa Coordinator | LoginViewController.delegate |
Mga problemang nalulutas ng Coordinator — Massive View Controller (nabigasyon — karaniwang dahilan ng paglaki ng controller). Sa karaniwang UIKit, ang ViewController ay naglalaman ng prepareForSegue, mga delegado ng nabigasyon, pagproseso ng unwind segues. Inaalis ito ng Coordinator. Ang Segue sa storyboard — isang static na ugnayan sa pagitan ng mga screen, ang Coordinator ay nagbibigay ng dinamikong nabigasyon na may mga kondisyon. Ang pag-test ng nabigasyon ay nagiging posible: maaaring subukan ang Coordinator nang walang UI sa pamamagitan ng pagsusuri ng pagkakasunod-sunod ng mga tawag sa Router.
Batayang Coordinator sa Swift — isang protocol na may kaugnay na uri para sa Router at mga paraang start/finish. Router — isang protocol na nag-aabstrak ng UINavigationController. Ang konkretong implementasyon ng Router ay bumabalot sa UINavigationController at itinatagubilin dito ang mga paraan. Coordinator ay tumatanggap ng Router sa init at ginagamit ito para sa nabigasyon. Ang mga anak na koordineytor ay iniimbak sa array na childCoordinators para sa pamamahala ng siklo ng buhay.
// Router — abstraksyon ng nabigasyon
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 — pamamahala ng 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)
}
}
Paglikha ng Coordinator sa AppDelegate/SceneDelegate — AppDelegate o SceneDelegate ay lumilikha ng UINavigationController, binabalot ito sa NavigationRouter, lumilikha ng ugat na Coordinator (AppCoordinator) at tumatawag ng start(). AppCoordinator ang nagpapasya kung ipapakita ang onboarding, pag-login o pangunahing screen — depende sa estado ng aplikasyon. Coordinator — ang tanging punto ng pagpasok para sa nabigasyon, ang ViewController ay hindi alam ang tungkol sa iba pang mga screen.
Hirarkiya ng mga koordineytor — AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator. Ang anak na koordineytor ay nilikha ng magulang at iniimbak sa array na childCoordinators. Kapag natapos ng anak na koordineytor ang kanyang gawain, tinatawag niya ang finish() sa magulang, at tinatanggal siya ng magulang mula sa childCoordinators. Ito ay pumipigil sa mga tagas ng memorya: ang Coordinator ay may malakas na sanggunian sa ViewController (sa pamamagitan ng Router), at kung hindi tatanggalin mula sa childCoordinators, ang bagay ay hindi mapapalaya.
// Delegado para sa komunikasyon ng Coordinator -> Parent
protocol AuthCoordinatorDelegate: AnyObject {
func authCoordinatorFinished(_ coordinator: AuthCoordinator)
}
class AuthCoordinator: CoordinatorProtocol {
weak var delegate: AuthCoordinatorDelegate?
func finish() {
delegate?.authCoordinatorFinished(self)
}
}
// AppCoordinator — magulang
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()
}
}
Pamamahala ng childCoordinators — pagtanggal ng Coordinator mula sa array ay ang tanging paraan upang mapalaya ito. Kung makalimutan mong tanggalin ang natapos na Coordinator, nananatili ito sa memorya kasama ng mga ViewController. Inirerekomenda: didMove(toParent:) ng magulang, callback sa pagkumpleto, o Combine publisher para sa awtomatikong pagtanggal. Ang pattern ng Coordinator ay hindi tumutukoy ng mekanismo ng abiso — delegate, closure o Combine — ang pagpili ay nasa developer.
Paglilipat ng data sa pamamagitan ng delegado — ang anak na Coordinator ay tumutukoy ng isang protocol ng delegado na may mga paraan kung saan inililipat ang mga resulta: func authCoordinator(_:didLoginWith user: User). Ang magulang ay nag-iimplementa ng protocol at tumatanggap ng data sa pagkumpleto ng flow ng anak. Ito ay ligtas sa uri at malinaw. Kahinaan: para sa bawat anak na Coordinator kailangang sumulat ng hiwalay na protocol. Para sa mga proyekto na may 10+ Coordinator, ito ay humahantong sa pagtaas ng bilang ng mga file.
Paglilipat ng data sa pamamagitan ng Result na uri — ang paraang finish ay tumatanggap ng Result
| Paraan | Mga kalamangan | Mga kahinaan |
|---|---|---|
| Delegate | Ligtas sa uri, malinaw, hiwalay na mga protocol | Maraming protocol, maraming boilerplate |
| Closure | Kompak, mas kaunting file | Mahirap i-debug ang retain cycle |
| Combine/Rx | Reaktibo, madaling pagsamahin | Dependensya sa library, mas mahirap ang Debug |
Pinagsasaluhang layer ng data — Ang mga Coordinator ay hindi direktang naglilipat ng data, ngunit gumagamit ng pinagsasaluhang serbisyo/repositoryo. Ang AuthCoordinator ay nag-iimbak ng token sa Keychain/UserDefaults, ang ProfileCoordinator ay nagbabasa mula doon. Ang mga Coordinator ay nakikipag-ugnayan sa pamamagitan ng pinagsasaluhang estado (Dependency Injection container), hindi sa pamamagitan ng direktang tawag. Ito ay nagbabawas ng pagkakadugtong ng mga Coordinator, ngunit lumilikha ng mga implicit na dependensya sa pinagsasaluhang estado.
Coordinator vs Router — Router ay isang bahagi ng Coordinator na nag-aabstrak ng UINavigationController. Coordinator ang responsable para sa flow (aling screen ang ipapakita), Router — para sa mekanika (paano ipapakita: push/present). Router ay „paano“, Coordinator ay „ano“. Maaaring gamitin ang Router nang walang Coordinator (hal., Navigator-singleton), ngunit ang Coordinator na walang Router — isa lamang ViewController na may ibang abstraksyon. Karaniwang ginagamit ang parehong pattern nang magkasama.
Coordinator vs VIPER — Ang VIPER ay may bahaging Wireframe na responsable para sa nabigasyon — kahalintulad ng Coordinator. Sa VIPER, ang Wireframe ay bahagi ng modyul, Coordinator — isang hiwalay na layer sa itaas ng mga modyul. Ang modyul ng VIPER (View-Interactor-Presenter-Entity-Router) ay may kasamang nabigasyon bilang bahagi ng modyul. Ang Coordinator ay panlabas sa mga modyul: lumilikha at nag-uugnay ng mga modyul, ngunit hindi kabilang sa kanilang komposisyon. Ang Coordinator ay mas nababaluktot para sa muling paggamit ng mga screen sa iba't ibang flow.
MVVM-C — pagpapalawak ng MVVM gamit ang Coordinator. Ang ViewModel ay hindi direktang alam ang Coordinator — ang ViewController ay nagtatalaga ng nabigasyon sa pamamagitan ng ViewModel, ang ViewModel ay tumatawag sa koordineytor sa pamamagitan ng isang protocol. MVVM-C — ang karaniwang approach para sa mga proyekto ng iOS na may SwiftUI: Coordinator ang namamahala ng NavigationStack o fullScreenCover, ang ViewModel ay tumatawag sa koordineytor sa pamamagitan ng pag-publish ng estado. Hindi inirerekomenda ng Apple ang Coordinator para sa SwiftUI — NavigationStack at NavigationPath ay mga built-in na mekanismo ng nabigasyon.
// MVVM-C: ViewModel ay tumatawag sa Coordinator sa pamamagitan ng protocol
protocol AuthNavigationProtocol: AnyObject {
func showMainScreen()
func showForgotPassword()
}
class AuthViewModel: ObservableObject {
weak var navigation: AuthNavigationProtocol?
func loginTapped() {
// lohika...
navigation?.showMainScreen()
}
}
Mga madalas itanong
Para sa SwiftUI, ang built-in na nabigasyon (NavigationStack, NavigationPath) ay kadalasang pumapalit sa Coordinator. Inirerekomenda ng Apple ang path-based na nabigasyon. Ang Coordinator ay may saysay para sa masalimuot na flow na may malalalim na kondisyon (onboarding-login-pangunahing screen depende sa papel). Para sa simpleng mga aplikasyon sa SwiftUI, ang Coordinator ay labis — gamitin ang NavigationPath.
Hindi, ito ay magkaibang mga pattern. Coordinator ang namamahala ng flow ng nabigasyon: nagpapasya kung aling screen ang ipapakita, lumilikha ng ViewController at nag-uugnay ng mga ito. Router — abstraksyon sa UINavigationController: push, present, pop, dismiss. Ginagamit ng Coordinator ang Router para isagawa ang nabigasyon. Sa ilang implementasyon, ang Router ay may kasamang lohika ng Coordinator (Router-per-screen), ngunit ito ay paglihis mula sa orihinal na pattern.
Dalawang pangunahing lugar ng tagas: childCoordinators (hawak ng magulang ang anak, nakakalimutang tanggalin) at Router (hawak ng UINavigationController ang ViewController). Solusyon: laging tanggalin ang anak na Coordinator mula sa array sa finish(). Gumamit ng weak na sanggunian para sa delegate. Para sa Router — huwag magtago ng malakas na sanggunian sa UINavigationController kung nasa hirarkiya na ito ng window. Subukan ang deinit ng Coordinator.
Para sa mga aplikasyon na may 3-5 screen, labis ang Coordinator — ang segue o simpleng navigationController.pushViewController ay mas simple. Para sa mga aplikasyon ng SwiftUI na may NavigationStack — labis din. Ang Coordinator ay makatwiran para sa mga aplikasyon na may 15+ screen, masalimuot na flow (onboarding na may mga sanga, awtorisasyon na may pagbawi ng password) at pinaghalong proyekto ng UIKit/SwiftUI.
Mock Router — pagsusuri kung aling mga paraan ang tinatawag at sa anong mga parameter. Pagsusuri ng childCoordinators: pagkatapos ng start() ang array ay hindi walang laman, pagkatapos ng finish() — walang laman. Ang Coordinator ay sinusubok nang walang UI: ang Router ay isang protocol, ang mock nito ay hindi nangangailangan ng UIKit. Gamitin ang XCTestExpectation para sa async flow. Sa Android analog — pagsubok ng NavigationController at NavHost na may mock-nabigasyon.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din