Azimjon Abdurasulov

03. MVVM + Coordinator — Navigatsiya Boshqaruvi

Arxitektura

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
  2. Coordinator nima?
  3. Coordinator Protocol
  4. AppCoordinator
  5. Child Coordinator'lar
  6. ViewModel -> Coordinator aloqasi
  7. Amaliy loyiha: To'liq MVVM + Coordinator app
  8. TabBar bilan Coordinator
  9. Deep Linking bilan Coordinator
  10. Coordinator kamchiliklari
  11. Xulosa
  12. 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.

// 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

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.

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.

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:

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.

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


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.

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.

@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.

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 afzalliklariKamchiliklari
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
  3. Apple Developer Documentation - UINavigationController, UIWindow

Markdown manba

iOS Lessons