# iOS Arxitektura 3: MVVM + Coordinator — Navigatsiya Boshqaruvi va Ekranlar Orasidagi O'tish

> **Holat:** Ushbu dars 2026-yil oktabr holatiga ko'ra eng dolzarb iOS arxitektura amaliyotlari (xususan, UIKit va MVVM-C naqshi) asosida tayyorlangan. Swift 6 va iOS 17+ imkoniyatlaridan to'liq foydalanilgan.

## Mundarija
1. [Navigatsiya muammosi](#navigatsiya-muammosi)
2. [Coordinator nima?](#coordinator-nima)
3. [Coordinator Protocol](#coordinator-protocol)
4. [AppCoordinator](#appcoordinator)
5. [Child Coordinator'lar](#child-coordinators)
6. [ViewModel -> Coordinator aloqasi](#vm-coordinator-aloqasi)
7. [Amaliy loyiha: To'liq MVVM + Coordinator app](#amaliy-loyiha)
8. [TabBar bilan Coordinator](#tabbar-coordinator)
9. [Deep Linking bilan Coordinator](#deep-linking)
10. [Coordinator kamchiliklari](#kamchiliklar)
11. [Xulosa](#xulosa)
12. [Manbalar](#manbalar)

---

## 1. Navigatsiya muammosi {#navigatsiya-muammosi}

Klassik MVC yoki oddiy MVVM arxitekturasida bitta katta muammo bor: **navigatsiya boshqaruvi (Routing)** asosan `ViewController` ichida amalga oshiriladi.

Faraz qilaylik, bizda `HomeViewController` bor va u foydalanuvchini `DetailViewController` ga o'tkazishi kerak.

```swift
// Antipattern: Yomon amaliyot
class HomeViewController: UIViewController {
    func didSelectUser(_ user: User) {
        let detailVC = DetailViewController()
        detailVC.user = user // Tight coupling!
        self.navigationController?.pushViewController(detailVC, animated: true)
    }
}
```

**Bu yondashuvning muammolari:**
- **Tight Coupling (Qattiq bog'liqlik):** `HomeViewController` endi `DetailViewController` haqida to'liq biladi (uni qanday yaratish, unga nima kerakligi). Ular bir-biriga qattiq bog'lanib qolgan.
- **Qayta foydalanish (Reusability):** Agar `DetailViewController` ni boshqa joydan chaqirmoqchi bo'lsak, yana shu kodni takrorlashimiz kerak.
- **ViewModel UIKit'ni bilmasligi kerak:** Agar biz navigatsiyani `ViewModel` ichiga olib kirsak, u UIKit (`UINavigationController`) ga bog'lanib qoladi. ViewModel UI elementlaridan xoli bo'lishi shart!

Yechim: Navigatsiyani alohida ob'ektga — **Coordinator**'ga topshirish.

---

## 2. Coordinator nima? {#coordinator-nima}

**Coordinator (Muvofiqlashtiruvchi)** — bu butun ilova yoki uning bir qismidagi navigatsiyani va oqimni (flow) boshqaradigan alohida ob'ekt.

Coordinator yondashuvida `ViewController`'lar bir-birini **MUTLAQO BILMAYDI**. Ularning yagona vazifasi — ma'lumotni ko'rsatish (UI) va foydalanuvchi harakatlarini `ViewModel` ga yetkazish. `ViewModel` esa kerak bo'lganda `Coordinator` orqali keyingi ekranga o'tishni so'raydi.

**Coordinator arxitekturasi daraxti:**

![Coordinator Ierarxiyasi](/lessons/images/architecture/arch_03_coordinator_tree.svg)

> **Senior eslatmasi:** Har doim ham har bir ekran uchun alohida Coordinator ochish shart emas. Kichikroq oqimlar uchun bitta Coordinator bir nechta ekranni boshqarishi mumkin (masalan, `HomeCoordinator` ham ro'yxatni, ham detail ekranni ko'rsatishi mumkin).

---

## 3. Coordinator Protocol {#coordinator-protocol}

App'dagi barcha Coordinator'lar bir xil shablonga ega bo'lishi uchun, biz `Coordinator` protokolini yaratamiz.

```swift
import UIKit

@MainActor
protocol Coordinator: AnyObject {
    /// O'zidan kichik coordinator'larni ushlab turish uchun array (Memory management uchun muhim!)
    var childCoordinators: [Coordinator] { get set }
    
    /// Ushbu coordinator boshqaradigan asosiy navigation controller
    var navigationController: UINavigationController { get }
    
    /// Coordinator o'z ishi va navigatsiyani boshlashi uchun metod
    func start()
    
    /// Coordinator o'z ishini tugatganda uni tozalash
    func finish()
}

extension Coordinator {
    func finish() {
        childCoordinators.removeAll()
    }
    
    func addChild(_ coordinator: Coordinator) {
        childCoordinators.append(coordinator)
    }
    
    func removeChild(_ coordinator: Coordinator) {
        childCoordinators.removeAll { $0 === coordinator }
    }
}
```

> **Muhim:** `childCoordinators` massivi `Coordinator` ob'ektlarini xotirada ushlab turish (retain) uchun juda muhim. Aks holda ular ARC tomonidan o'chirib yuboriladi va navigatsiya buziladi.

---

## 4. AppCoordinator {#appcoordinator}

`AppCoordinator` — ilovaning boshlanish nuqtasi. U `UIWindow` ni oladi va foydalanuvchi holatiga qarab (masalan, avtorizatsiyadan o'tganmi yoki yo'q) tegishli child coordinator'ga yo'naltiradi.

```swift
import UIKit

@MainActor
final class AppCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    var navigationController: UINavigationController
    private let window: UIWindow
    
    init(window: UIWindow) {
        self.window = window
        self.navigationController = UINavigationController()
    }
    
    func start() {
        window.rootViewController = navigationController
        window.makeKeyAndVisible()
        
        let isLoggedIn = UserDefaults.standard.bool(forKey: "isLoggedIn")
        
        if isLoggedIn {
            showHomeFlow()
        } else {
            showAuthFlow()
        }
    }
    
    private func showAuthFlow() {
        // AuthCoordinator ni yaratamiz va ishga tushiramiz
        // To'liq implementatsiyasi keyingi qismlarda ko'rsatilgan
    }
    
    private func showHomeFlow() {
        // HomeCoordinator ni yaratamiz va ishga tushiramiz
    }
}
```

SceneDelegate da qanday chaqiriladi:

```swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    var appCoordinator: AppCoordinator?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        let window = UIWindow(windowScene: windowScene)
        self.window = window
        
        appCoordinator = AppCoordinator(window: window)
        appCoordinator?.start()
    }
}
```

---

## 5. Child Coordinator'lar {#child-coordinators}

Endi biz o'ziga xos flow'larni (oqimlarni) yaratamiz.

**AuthCoordinator:** Faqatgina Login va Register ekranlarini boshqaradi.

```swift
import UIKit

@MainActor
protocol AuthCoordinatorDelegate: AnyObject {
    func didFinishAuth(_ coordinator: AuthCoordinator)
}

@MainActor
final class AuthCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    var navigationController: UINavigationController
    
    weak var delegate: AuthCoordinatorDelegate?
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        showLogin()
    }
    
    private func showLogin() {
        let viewModel = LoginViewModel()
        
        // ViewModel -> Coordinator aloqasi closure orqali
        viewModel.onLoginSuccess = { [weak self] in
            self?.delegate?.didFinishAuth(self!)
        }
        
        viewModel.onRegisterRequested = { [weak self] in
            self?.showRegister()
        }
        
        let vc = LoginViewController(viewModel: viewModel)
        navigationController.setViewControllers([vc], animated: false)
    }
    
    private func showRegister() {
        // Register ekranini ko'rsatish
        print("Register ekrani ochildi")
    }
}
```

---

## 6. ViewModel -> Coordinator aloqasi {#vm-coordinator-aloqasi}

`ViewModel` va `Coordinator` bir-biri bilan qanday gaplashadi?
ViewModel navigatsiya qilish kerakligini biladi, lekin qanday qilishni emas. Buning uchun asosan 2 ta usul ishlatiladi:
1. **Delegate Protocol:** ViewModel delegate protokoli e'lon qiladi, Coordinator unga conform bo'ladi.
2. **Closures (Callback):** ViewModel o'zida block/closure e'lon qiladi, Coordinator uni to'ldiradi. (Biroz qulayroq va qisqaroq).

![MVVM + Coordinator O'zaro Aloqa Oqimi](/lessons/images/architecture/arch_03_coordinator_flow.svg)

---

## 7. Amaliy loyiha: To'liq MVVM + Coordinator app {#amaliy-loyiha}

Quyida to'liq ishlaydigan va kompilyatsiya bo'ladigan Home flow kodi keltirilgan. Model, ViewModel, ViewController va Coordinator to'liq bog'langan.

```swift
import UIKit

// MARK: - 1. Model
struct User: Identifiable {
    let id: UUID = UUID()
    let name: String
    let email: String
}

// MARK: - 2. ViewModels
@MainActor
final class UserListViewModel {
    // Navigatsiya uchun callback'lar (Coordinator to'ldiradi)
    var onUserSelected: ((User) -> Void)?
    var onLogout: (() -> Void)?
    
    // UI update uchun closure
    var onDataUpdated: (() -> Void)?
    
    private(set) var users: [User] = []
    
    func fetchUsers() {
        // Fake tarmoq so'rovi
        self.users = [
            User(name: "Alisher Navoiy", email: "alisher@navoiy.uz"),
            User(name: "Zahiriddin Bobur", email: "bobur@andijon.uz")
        ]
        onDataUpdated?()
    }
    
    func selectUser(at index: Int) {
        let user = users[index]
        onUserSelected?(user) // Coordinator'ga xabar beramiz
    }
    
    func logout() {
        onLogout?() // Coordinator'ga xabar beramiz
    }
}

@MainActor
final class UserDetailViewModel {
    let user: User
    
    init(user: User) {
        self.user = user
    }
}

// MARK: - 3. ViewControllers
final class UserListViewController: UIViewController {
    private let viewModel: UserListViewModel
    private let tableView = UITableView()
    
    init(viewModel: UserListViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        bindViewModel()
        viewModel.fetchUsers()
    }
    
    private func setupUI() {
        title = "Foydalanuvchilar"
        view.backgroundColor = .systemBackground
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Chiqish", style: .plain, target: self, action: #selector(logoutTapped))
        
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        view.addSubview(tableView)
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
    
    private func bindViewModel() {
        viewModel.onDataUpdated = { [weak self] in
            self?.tableView.reloadData()
        }
    }
    
    @objc private func logoutTapped() {
        viewModel.logout()
    }
}

extension UserListViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.users.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = viewModel.users[indexPath.row].name
        return cell
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        viewModel.selectUser(at: indexPath.row)
    }
}

final class UserDetailViewController: UIViewController {
    private let viewModel: UserDetailViewModel
    
    init(viewModel: UserDetailViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        title = viewModel.user.name
        
        let label = UILabel()
        label.text = viewModel.user.email
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

// MARK: - 4. HomeCoordinator
@MainActor
protocol HomeCoordinatorDelegate: AnyObject {
    func didLogout(_ coordinator: HomeCoordinator)
}

@MainActor
final class HomeCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    var navigationController: UINavigationController
    
    weak var delegate: HomeCoordinatorDelegate?
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        let viewModel = UserListViewModel()
        
        // 1. Detail ekranga o'tish marshruti
        viewModel.onUserSelected = { [weak self] user in
            self?.showUserDetail(user)
        }
        
        // 2. Logout marshruti
        viewModel.onLogout = { [weak self] in
            guard let self = self else { return }
            self.delegate?.didLogout(self)
        }
        
        let vc = UserListViewController(viewModel: viewModel)
        navigationController.setViewControllers([vc], animated: true)
    }
    
    private func showUserDetail(_ user: User) {
        let viewModel = UserDetailViewModel(user: user)
        let vc = UserDetailViewController(viewModel: viewModel)
        navigationController.pushViewController(vc, animated: true)
    }
}
```

Bu amaliyotda ko'rganingizdek, `UserListViewController` qanday qilib Detail ekranga o'tish haqida hech narsa bilmaydi. U faqat ViewModel'ga `selectUser` qilinganini xabar qiladi. ViewModel esa `onUserSelected` closure'ni chaqiradi. Coordinator haqiqiy `pushViewController` amalni bajaradi.

---

## 8. TabBar bilan Coordinator {#tabbar-coordinator}

Agar ilovangizda TabBar bo'lsa, `TabBarCoordinator` yaratish juda keng tarqalgan amaliyotdir. TabBar o'z ichida bir nechta NavigationController'larni saqlaydi va har biri alohida Child Coordinator tomonidan boshqariladi.

```swift
@MainActor
final class TabBarCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    var navigationController: UINavigationController
    private var tabBarController: UITabBarController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
        self.tabBarController = UITabBarController()
    }
    
    func start() {
        // Tab 1: Home
        let homeNav = UINavigationController()
        homeNav.tabBarItem = UITabBarItem(title: "Asosiy", image: UIImage(systemName: "house"), tag: 0)
        let homeCoordinator = HomeCoordinator(navigationController: homeNav)
        addChild(homeCoordinator)
        homeCoordinator.start()
        
        // Tab 2: Profile
        let profileNav = UINavigationController()
        profileNav.tabBarItem = UITabBarItem(title: "Profil", image: UIImage(systemName: "person"), tag: 1)
        let profileCoordinator = ProfileCoordinator(navigationController: profileNav)
        addChild(profileCoordinator)
        profileCoordinator.start()
        
        tabBarController.viewControllers = [homeNav, profileNav]
        
        // TabBar'ni asosiy qilib ko'rsatamiz
        navigationController.setNavigationBarHidden(true, animated: false)
        navigationController.pushViewController(tabBarController, animated: false)
    }
}
```

---

## 9. Deep Linking bilan Coordinator {#deep-linking}

AppCoordinator Deep Linking (Universal Links yoki URL Schemes) larni boshqarish uchun eng zo'r joy. 

Faraz qilaylik, bizga `myapp://user/123` degan link keldi.

```swift
extension AppCoordinator {
    func handleDeepLink(url: URL) {
        guard let host = url.host else { return }
        
        if host == "user" {
            // ID ni ajratib olamiz
            let path = url.path.replacingOccurrences(of: "/", with: "")
            if let userId = Int(path) {
                // HomeFlow'ga o'tishni va aynan o'sha profilni ochishni so'raymiz
                showUserFromDeepLink(id: userId)
            }
        }
    }
}
```

> **Muhim:** Deep link orqali kelganda, Coordinator'lar zanjiri bo'ylab tegishli coordinator'ni topish va unga marshrutlash vazifasi ancha murakkablashadi. Buning uchun ko'pincha `Router` kabi qo'shimcha yordamchi sinflar kiritilishi mumkin.

---

## 10. Coordinator kamchiliklari {#kamchiliklar}

| MVVM + Coordinator afzalliklari | Kamchiliklari |
| :--- | :--- |
| **Separation of Concerns:** View faqat UI, ViewModel faqat logika, Coordinator faqat navigatsiya. | **Boilerplate Kod:** Juda ko'p yozish kerak (protocol, delegate/closure). Kichik loyihalar uchun over-engineering. |
| **Reusability:** Bitta Ekranni turli xil oqimlarda (masalan onboarding'da va settings'da) ishlatish oson. | **Memory Leaks:** `childCoordinators` to'g'ri tozalanmasa (arraydan o'chirib tashlanmasa), jiddiy memory leak kelib chiqadi. |
| **Deep Link yordami:** Barcha marshrutlash bir joydan boshqarilgani uchun juda qulay. | **SwiftUI integratsiyasi:** SwiftUI asosan state-driven navigatsiyani targ'ib qiladi (`NavigationStack`, `navigationDestination`), Coordinator pattern esa imperative yondashuv. SwiftUI 4+ da buni Router pattern yordamida zamonaviylashtirish mumkin. |

---

## Xulosa {#xulosa}

Coordinator Pattern — bu katta va o'rta o'lchamdagi iOS loyihalari uchun navigatsiya muammolariga standart echim. U ViewModel'ni UIKit'dan ajratadi va kodni mustaqil ravishda test qilinadigan (testable) modullarga bo'lib yuboradi. 

Garchi bir oz boshlang'ich sozlash (boilerplate) talab qilsa-da, arxitekturani masshtablash (scaling) jarayonida Coordinator'ning foydasi o'zini oqlaydi.

---

## Manbalar {#manbalar}

1. Soroush Khanlou - "The Coordinator" (Original g'oya muallifi)
2. Paul Hudson - [How to use the coordinator pattern in iOS apps](https://www.hackingwithswift.com/articles/71/how-to-use-the-coordinator-pattern-in-ios-apps)
3. Apple Developer Documentation - `UINavigationController`, `UIWindow`
