# iOS Arxitektura 2: MVVM — Model-View-ViewModel, Data Binding va Zamonaviy iOS Dasturlash

> **Holat:** 2026-yil oktabr. MVVM iOS dasturlashda eng ko'p qo'llaniladigan, ayniqsa SwiftUI va Combine texnologiyalari bilan birga eng yaxshi moslashadigan va sanoat standartiga aylangan arxitektura hisoblanadi.

## Mundarija
1. [MVVM nima?](#mvvm-nima)
2. [ViewModel qoidalari](#viewmodel-qoidalari)
3. [Data Binding usullari (iOS 2024-2026)](#data-binding)
4. [Amaliy loyiha: User List MVVM (Combine binding)](#loyihauikit)
5. [Amaliy loyiha 2: Xuddi shu app SwiftUI + MVVM](#loyihaswiftui)
6. [MVVM + Input/Output pattern](#input-output)
7. [Unit Testing ViewModel](#unit-testing)
8. [MVVM kamchiliklari](#kamchiliklar)
9. [MVVM vs MVC taqqoslash jadvali](#taqqoslash)
10. [Xulosa](#xulosa)
11. [Manbalar](#manbalar)

---

## 1. MVVM nima? {#mvvm-nima}

**MVVM (Model-View-ViewModel)** — bu dasturning mantiqiy (biznes) qismini va foydalanuvchi interfeysini (UI) bir-biridan ajratishga yordam beruvchi arxitektura paternidir. MVC'dan farqli ravishda, MVVM UI va biznes logikani butunlay mustaqil qiladi.

- **Model:** Ma'lumotlar tuzilmasi (struct'lar, Core Data entity'lari, networking modellari).
- **View:** UI (UIViewController va uning ichidagi UIView'lar YOKI SwiftUI View). Foydalanuvchi harakatlarini qabul qiladi va ViewModel'ga uzatadi.
- **ViewModel:** View uchun ma'lumotlarni tayyorlaydigan qatlam. Model'dan ma'lumotni oladi, uni View tushunadigan ko'rinishga (String, Bool va h.k.) o'zgartiradi.

![MVVM Arxitekturasi va Ma'lumotlar Oqimi](/lessons/images/architecture/arch_02_mvvm_flow.svg)

> **Muhim:** View ViewModel'ni biladi va unga bog'langan (Reference bor), lekin ViewModel View'ni umuman BILMAYDI! ViewModel'da UI elementlari haqida hech qanday tasavvur yo'q.

## 2. ViewModel qoidalari {#viewmodel-qoidalari}

Haqiqiy MVVM'ni yashiringan MVC'dan ajratib turadigan qat'iy qoidalar mavjud:

1. **ViewModel UIKit import QILMASLIGI kerak:** ViewModel faylida `import UIKit` degan yozuv bo'lishi mumkin emas (Foundation yoki Combine etarli).
2. **ViewModel faqat biznes logika va data transformation bajaradi:** Modelni olib, uni View ko'rsatishi mumkin bo'lgan holatga keltiradi.
3. **ViewModel'da UIColor, UIFont, UIImage yo'q:** Faqat oddiy turlar (String, Bool, Int, enum) ishlatilishi kerak. Masalan, rangni ko'rsatish uchun ViewModel `String` (hex) yoki `Enum` qaytarishi kerak.

> **Senior eslatmasi:** Agar ViewModel'ingiz ichida `UIView` yoki uning vorislariga reference bo'lsa, tabriklayman — siz MVVM emas, qo'rqinchli "Massive MVC" yozyapsiz!

## 3. Data Binding usullari (iOS 2024-2026) {#data-binding}

View va ViewModel'ni bir-biriga bog'lash (Data Binding) uchun zamonaviy iOS'da bir nechta usullar mavjud:

![Data Binding Mexanizmlari](/lessons/images/architecture/arch_02_data_binding.svg)

### a) @Observable (iOS 17+, ENG ZAMONAVIY)
Swift 5.9 bilan kelgan yangi makro orqali qilinadi. SwiftUI uchun eng toza usul, UIKit bilan ham manual ishlash mumkin.

```swift
import Foundation
import Observation

@Observable
final class UserViewModel {
    var name = ""
    var isLoading = false
}
```

### b) Combine (iOS 13+)
Reaktiv dasturlash orqali `Publisher` va `Subscriber` yordamida ishlash.

```swift
import Foundation
import Combine

final class CombineViewModel {
    @Published var users: [String] = []
    
    // View tomonida:
    // viewModel.$users.sink { [weak self] users in self?.updateUI() }
}
```

### c) Closures (eng oddiy, framework kerak emas)
Hech qanday qo'shimcha frameworklarsiz callback'lar orqali bog'lash.

```swift
final class ClosureViewModel {
    var onUsersUpdated: (([String]) -> Void)?
    var users: [String] = [] {
        didSet { onUsersUpdated?(users) }
    }
}
```

### d) async/await (Swift Concurrency)
Asinxron kodlar uchun eng yaxshi usul.

```swift
final class AsyncViewModel {
    func fetchUsers() async throws -> [String] {
        // Tarmoq so'rovi
        return ["Ali", "Vali"]
    }
    
    // View tomonida: Task { let users = try await viewModel.fetchUsers() }
}
```

## 4. Amaliy loyiha: User List MVVM (Combine binding) {#loyihauikit}

Quyida UIKit, SnapKit va Combine orqali qilingan to'liq ishlaydigan MVVM misoli:

```swift
import UIKit
import Combine
import SnapKit

// 1. Model
struct User: Identifiable, Codable {
    let id: Int
    let name: String
    let email: String
}

// 2. Service
protocol UserServiceProtocol {
    func fetchUsers() async throws -> [User]
}

final class UserService: UserServiceProtocol {
    func fetchUsers() async throws -> [User] {
        let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([User].self, from: data)
    }
}

// 3. ViewModel
@MainActor
final class UserListViewModel {
    @Published private(set) var users: [User] = []
    @Published private(set) var isLoading: Bool = false
    @Published private(set) var error: String?
    
    private let userService: UserServiceProtocol
    
    init(userService: UserServiceProtocol = UserService()) {
        self.userService = userService
    }
    
    func loadUsers() {
        isLoading = true
        error = nil
        
        Task {
            do {
                self.users = try await userService.fetchUsers()
            } catch {
                self.error = error.localizedDescription
            }
            isLoading = false
        }
    }
}

// 4. ViewController
final class UserListViewController: UIViewController {
    private let viewModel = UserListViewModel()
    private var cancellables = Set<AnyCancellable>()
    
    private let tableView = UITableView()
    private let activityIndicator = UIActivityIndicatorView(style: .large)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        bindViewModel()
        viewModel.loadUsers()
    }
    
    private func setupUI() {
        title = "Foydalanuvchilar"
        view.backgroundColor = .systemBackground
        
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        
        view.addSubview(tableView)
        view.addSubview(activityIndicator)
        
        tableView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        activityIndicator.snp.makeConstraints { make in
            make.center.equalToSuperview()
        }
    }
    
    private func bindViewModel() {
        viewModel.$users
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.tableView.reloadData()
            }
            .store(in: &cancellables)
        
        viewModel.$isLoading
            .receive(on: DispatchQueue.main)
            .sink { [weak self] isLoading in
                isLoading ? self?.activityIndicator.startAnimating() : self?.activityIndicator.stopAnimating()
            }
            .store(in: &cancellables)
            
        viewModel.$error
            .compactMap { $0 }
            .receive(on: DispatchQueue.main)
            .sink { [weak self] errorMessage in
                let alert = UIAlertController(title: "Xato", message: errorMessage, preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default))
                self?.present(alert, animated: true)
            }
            .store(in: &cancellables)
    }
}

extension UserListViewController: UITableViewDataSource {
    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)
        let user = viewModel.users[indexPath.row]
        var content = cell.defaultContentConfiguration()
        content.text = user.name
        content.secondaryText = user.email
        cell.contentConfiguration = content
        return cell
    }
}
```

## 5. Amaliy loyiha 2: Xuddi shu app SwiftUI + MVVM {#loyihaswiftui}

Eng qizig'i shundaki, to'g'ri yozilgan `UserListViewModel` nafaqat UIKit'da, balki SwiftUI'da ham hech qanday o'zgarishsiz ishlaydi! (Faqat u `ObservableObject` bo'lishi kerak, shuning uchun tepadagi kodga `ObservableObject` qoshilgan deb faraz qilamiz).

```swift
import SwiftUI

// ViewModel SwiftUI uchun ham huddi shunday ishlaydi
// class UserListViewModel: ObservableObject ...

struct UserListView: View {
    @StateObject private var viewModel = UserListViewModel()
    
    var body: some View {
        NavigationView {
            Group {
                if viewModel.isLoading {
                    ProgressView("Yuklanmoqda...")
                } else {
                    List(viewModel.users) { user in
                        VStack(alignment: .leading) {
                            Text(user.name)
                                .font(.headline)
                            Text(user.email)
                                .font(.subheadline)
                                .foregroundColor(.secondary)
                        }
                    }
                }
            }
            .navigationTitle("Foydalanuvchilar")
            .onAppear {
                viewModel.loadUsers()
            }
            .alert("Xato", isPresented: .constant(viewModel.error != nil)) {
                Button("OK") { /* xatoni tozalash mantiqi */ }
            } message: {
                Text(viewModel.error ?? "")
            }
        }
    }
}
```

## 6. MVVM + Input/Output pattern {#input-output}

Katta loyihalarda ViewModel interfeysini toza saqlash uchun Input va Output protokollari qo'llaniladi.

```swift
import Combine

protocol UserListViewModelInput {
    func viewDidLoad()
    func didSelectUser(at index: Int)
}

protocol UserListViewModelOutput {
    var users: AnyPublisher<[User], Never> { get }
    var isLoading: AnyPublisher<Bool, Never> { get }
}

protocol UserListViewModelType {
    var input: UserListViewModelInput { get }
    var output: UserListViewModelOutput { get }
}

// Amalga oshirish:
final class UserListViewModelIO: UserListViewModelType, UserListViewModelInput, UserListViewModelOutput {
    var input: UserListViewModelInput { self }
    var output: UserListViewModelOutput { self }
    
    // Outputs
    private let usersSubject = CurrentValueSubject<[User], Never>([])
    var users: AnyPublisher<[User], Never> { usersSubject.eraseToAnyPublisher() }
    
    private let loadingSubject = CurrentValueSubject<Bool, Never>(false)
    var isLoading: AnyPublisher<Bool, Never> { loadingSubject.eraseToAnyPublisher() }
    
    // Inputs
    func viewDidLoad() {
        loadingSubject.send(true)
        // fetch...
    }
    
    func didSelectUser(at index: Int) {
        let selected = usersSubject.value[index]
        print("Selected: \(selected.name)")
    }
}
```
Bu yondashuv kod o'qilishini ancha osonlashtiradi va test yozishga juda qulay.

## 7. Unit Testing ViewModel {#unit-testing}

MVVM'ning eng katta yutuqlaridan biri — UI bo'lmagani uchun uni test qilish juda oson.

```swift
import XCTest
import Combine
@testable import MyApp

// 1. Mock Service yaratamiz
final class MockUserService: UserServiceProtocol {
    var shouldFail = false
    
    func fetchUsers() async throws -> [User] {
        if shouldFail {
            throw URLError(.badServerResponse)
        }
        return [User(id: 1, name: "Test User", email: "test@test.com")]
    }
}

// 2. Test yozamiz
@MainActor
final class UserListViewModelTests: XCTestCase {
    var sut: UserListViewModel! // System Under Test
    var mockService: MockUserService!
    
    override func setUp() {
        super.setUp()
        mockService = MockUserService()
        sut = UserListViewModel(userService: mockService)
    }
    
    func test_loadUsers_success_updatesUsers() async {
        // Arrange
        mockService.shouldFail = false
        
        // Act
        sut.loadUsers()
        
        // async method tugashini biroz kutamiz
        try? await Task.sleep(nanoseconds: 100_000_000)
        
        // Assert
        XCTAssertFalse(sut.isLoading)
        XCTAssertEqual(sut.users.count, 1)
        XCTAssertEqual(sut.users.first?.name, "Test User")
        XCTAssertNil(sut.error)
    }
}
```

## 8. MVVM kamchiliklari {#kamchiliklar}

Har qanday arxitektura singari MVVM ham mukammal emas:
1. **Kichik loyihalarda ortiqcha murakkablik:** Bitta tugma va bitta text bo'lgan ekranga MVVM yozish — ortiqcha vaqt sarfi.
2. **Binding boilerplate:** Combine yoki tayyor framework'larsiz closure/KVO orqali binding qilish juda ko'p qaytariluvchi kod (boilerplate) yozishni talab qiladi.
3. **Massive ViewModel xavfi:** Agar ehtiyot bo'lmasangiz, Massive View Controller muammosi endi "Massive ViewModel" muammosiga aylanadi.
4. **Navigatsiya:** MVVM'ning o'zida ekrandan-ekranga o'tish (routing) uchun aniq qoida yo'q (Shuning uchun Coordinator pattern bilan birga ishlatiladi).

## 9. MVVM vs MVC taqqoslash jadvali {#taqqoslash}

| Xususiyat | MVC | MVVM |
| :--- | :--- | :--- |
| **Rol ajratish** | O'rtacha (Controller juda ko'p ish qiladi) | Zo'r (Logika to'liq ajratilgan) |
| **Test qilish qulayligi** | Qiyin (UI bilan aralashgan) | Juda oson (ViewModel'da UI yo'q) |
| **Binding** | Yo'q (Manual update qilinadi) | Bor (Avtomatik yangilanish) |
| **O'rganish tezligi** | Juda oson | O'rtacha (Binding'ni o'rganish kerak) |
| **SwiftUI mosligi** | Yomon moslashadi | Tabiiy moslashgan |

## Xulosa {#xulosa}

MVVM arxitekturasi zamonaviy iOS dasturlashning "oltin standarti" hisoblanadi. U kodingizni toza, o'qilishi oson va eng muhimi — test qilinadigan holatga keltiradi. ViewModel'da `UIKit`ni qat'iy cheklash va to'g'ri Data Binding texnikasini tanlash muvaffaqiyatli MVVM loyihaning asosiy kalitidir.

## Manbalar {#manbalar}
- Apple Documentation: [Observation](https://developer.apple.com/documentation/observation)
- Apple Documentation: [Combine](https://developer.apple.com/documentation/combine)
- [Design Patterns in Swift - MVVM](https://refactoring.guru/design-patterns)
