02. MVVM — Data Binding va Reaktiv Holat
Arxitektura
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
- MVVM nima?
- ViewModel qoidalari
- Data Binding usullari (iOS 2024-2026)
- Amaliy loyiha: User List MVVM (Combine binding)
- Amaliy loyiha 2: Xuddi shu app SwiftUI + MVVM
- MVVM + Input/Output pattern
- Unit Testing ViewModel
- MVVM kamchiliklari
- MVVM vs MVC taqqoslash jadvali
- Xulosa
- 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
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:
- ViewModel UIKit import QILMASLIGI kerak: ViewModel faylida
import UIKitdegan yozuv bo'lishi mumkin emas (Foundation yoki Combine etarli). - ViewModel faqat biznes logika va data transformation bajaradi: Modelni olib, uni View ko'rsatishi mumkin bo'lgan holatga keltiradi.
- ViewModel'da UIColor, UIFont, UIImage yo'q: Faqat oddiy turlar (String, Bool, Int, enum) ishlatilishi kerak. Masalan, rangni ko'rsatish uchun ViewModel
String(hex) yokiEnumqaytarishi kerak.
Senior eslatmasi: Agar ViewModel'ingiz ichida
UIViewyoki 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
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.
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.
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.
final class ClosureViewModel {
var onUsersUpdated: (([String]) -> Void)?
var users: [String] = [] {
didSet { onUsersUpdated?(users) }
}
}
d) async/await (Swift Concurrency)
Asinxron kodlar uchun eng yaxshi usul.
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:
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).
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.
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.
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:
- Kichik loyihalarda ortiqcha murakkablik: Bitta tugma va bitta text bo'lgan ekranga MVVM yozish — ortiqcha vaqt sarfi.
- Binding boilerplate: Combine yoki tayyor framework'larsiz closure/KVO orqali binding qilish juda ko'p qaytariluvchi kod (boilerplate) yozishni talab qiladi.
- Massive ViewModel xavfi: Agar ehtiyot bo'lmasangiz, Massive View Controller muammosi endi "Massive ViewModel" muammosiga aylanadi.
- 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 UIKitni qat'iy cheklash va to'g'ri Data Binding texnikasini tanlash muvaffaqiyatli MVVM loyihaning asosiy kalitidir.
Manbalar {#manbalar}
- Apple Documentation: Observation
- Apple Documentation: Combine
- Design Patterns in Swift - MVVM