Azimjon Abdurasulov

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?

  1. ProductListViewController ni boshqa joyda (masalan, qidiruv natijalarida) qayta ishlatib bo'lmaydi, chunki u ProductDetailViewController ga qattiq bog'langan.
  2. Push-Notification orqali to'g'ridan-to'g'ri ProductDetail ni ochish kerak bo'lsa, butun navigatsiya logikasini boshidan qayta yozish kerak.
  3. 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, AuthCoordinator o'zining onFinish closure'ini chaqirsin va AppCoordinator asosiy ekranga o'tsin!

Markdown manba

iOS Lessons