13. Coordinator va Router: Massive VC Decoupling va Deep Linking
🏛️ iOS Design Patterns — Mukammal Enterprise & Architecture Qo‘llanma
13. Apple Native: Coordinator va Router Pattern
"Agar sizning ViewController'ingiz boshqa bir ViewController'ni qanday yaratishni va unga qanday o'tishni o'zi hal qilayotgan bo'lsa — siz navigatsiya botqog'idasiz. Coordinator ViewController'ni navigatsiya mas'uliyatidan 100% ozod qiladi."
1. Massive View Controller (MVC) va Qattiq Bog'liqlik Fojiasi
Klassik iOS loyihalarida eng ko'p uchraydigan xato:
// ❌ QATTIQ BOG'LIQLIK (TIGHT COUPLING):
class ProductListViewController: UIViewController {
func didSelectProduct(product: Product) {
// ProductListViewController boshqa ekranning ichki parametrlarini
// va navigatsiyani bilishga majbur bo'lmoqda!
let detailVC = ProductDetailViewController(productId: product.id)
detailVC.discountCode = "SPRING2026"
self.navigationController?.pushViewController(detailVC, animated: true)
}
}
Nega bu halokatli?
ProductListViewControllerni boshqa joyda (masalan, qidiruv natijalarida) qayta ishlatib bo'lmaydi, chunki uProductDetailViewControllerga qattiq bog'langan.- Push-Notification orqali to'g'ridan-to'g'ri
ProductDetailni ochish kerak bo'lsa, butun navigatsiya logikasini boshidan qayta yozish kerak. - Unit Testlarda navigatsiya sodir bo'lganini tekshirib bo'lmaydi.
2. Coordinator Pattern Arxitekturasi
2015-yilda Soroush Khanlou tomonidan taqdim etilgan Coordinator patterni butun navigatsiyani alohida obyektga topshiradi:
graph TD
AppCoord["AppCoordinator (Ilova Asosiy Koordinatori)"] --> AuthCoord["AuthCoordinator (Kirish / Ro'yxatdan O'tish)"]
AppCoord --> MainTabCoord["MainTabCoordinator (Asosiy Sahifalar)"]
MainTabCoord --> HomeCoord["HomeCoordinator"]
MainTabCoord --> OrdersCoord["OrdersCoordinator"]
MainTabCoord --> ProfileCoord["ProfileCoordinator"]
OrdersCoord --> OrderListVC["OrdersListViewController\n(Faqat UI chizadi)"]
OrdersCoord --> OrderDetailVC["OrderDetailViewController\n(Faqat UI chizadi)"]
style AppCoord fill:#131720,stroke:#bef264,stroke-width:2px
style OrdersCoord fill:#181d26,stroke:#7da2ce,stroke-width:1px
style OrderListVC fill:#181d26,stroke:#60a5fa,stroke-width:1px
Qoida: ViewController faqat bir narsani biladi: "Foydalanuvchi tugmani bosdi". Qayerga o'tishni esa faqat Coordinator hal qiladi!
3. Real Swift 6 Kodi: Universal Coordinator
import UIKit
// 1. Asosiy Koordinator Protokoli
@MainActor
public protocol Coordinator: AnyObject {
var childCoordinators: [any Coordinator] { get set }
var navigationController: UINavigationController { get set }
func start()
}
// 2. Buyurtmalar Koordinatori
@MainActor
public final class OrdersCoordinator: Coordinator {
public var childCoordinators: [any Coordinator] = []
public var navigationController: UINavigationController
public init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
public func start() {
let listVC = OrdersListViewController()
// ViewController koordinatorga xabar berishi uchun closure yoki delegat
listVC.onOrderSelected = { [weak self] orderId in
self?.showOrderDetail(orderId: orderId)
}
navigationController.pushViewController(listVC, animated: true)
}
public func showOrderDetail(orderId: String) {
let detailVC = OrderDetailViewController(orderId: orderId)
detailVC.onPaymentRequested = { [weak self] orderId in
self?.startPaymentFlow(orderId: orderId)
}
navigationController.pushViewController(detailVC, animated: true)
}
private func startPaymentFlow(orderId: String) {
// Yangi Child Coordinator (Modal to'lov oqimi)
let paymentNav = UINavigationController()
let paymentCoord = PaymentCoordinator(navigationController: paymentNav, orderId: orderId)
childCoordinators.append(paymentCoord)
paymentCoord.onFinish = { [weak self, weak paymentCoord] in
self?.childCoordinators.removeAll { $0 === paymentCoord }
paymentNav.dismiss(animated: true)
}
paymentCoord.start()
navigationController.present(paymentNav, animated: true)
}
}
// 3. Toza ViewController (Navigatsiyani umuman bilmaydi!)
public final class OrdersListViewController: UIViewController {
public var onOrderSelected: ((String) -> Void)?
func userTappedRow(orderId: String) {
// Faqat o'z vazifasini bajaradi va hodisani uzatadi
onOrderSelected?(orderId)
}
}
4. Deeplink Boshqaruvi: Nega Coordinator Eng Yaxshisi?
Tashqi veb-saytdan app://orders/details?id=772 havolasi kelganda: AppCoordinator havolani parslaydi va uni MainTabCoordinator -> OrdersCoordinator.showOrderDetail("772") ga uzatadi. Ekranda qanday o'tishlar bo'lishi 1 ta joyda nazorat qilinadi!
5. Interaktiv Savol-Javob (Self-Check Quiz)
<details> <summary><b>1-Savol: Coordinator patternida Child Coordinatorlar nima uchun 'childCoordinators' massivida saqlanishi kerak?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: Xotiradan o'chib ketmasligi uchun (Memory Lifecycle). Agar siz yangi PaymentCoordinator ochsangiz va uni massivda saqlamasangiz, funksiya tugashi bilanoq u xotiradan (ARC) o'chib ketadi va uning ichidagi delegat yoki closurelar ishlamay qoladi. Shuning uchun ota koordinator bolani massivda ushlab turadi, oqim tugagach esa massivdan o'chirib yuboradi (removeAll). </details>
<details> <summary><b>2-Savol: SwiftUI da Coordinator patterni qanday ishlatiladi?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: iOS 16+ da NavigationPath va @Observable AppRouter yaratiladi. Barcha View'lar routerga buyruq beradi (router.push(.orderDetail(id))), asosiy NavigationStack(path: $router.path) esa marshrutlar bo'yicha kerakli SwiftUI View'ni render qilib beradi. </details>
6. Amaliy Topshiriq (Senior Challenge)
AuthCoordinator sinfini yarating:
- Oqim:
LoginViewController-> agar hisob bo'lmasa ->RegisterViewController->OTPVerifyViewController. - Muvaffaqiyatli ro'yxatdan o'tgach,
AuthCoordinatoro'ziningonFinishclosure'ini chaqirsin vaAppCoordinatorasosiy ekranga o'tsin!