Published on

MVVM Arxitekturasida Async Await qanday ishlatiladi?

Authors

Nega MVVM?

iOS dasturlashda eng ko'p qo'llaniladigan arxitektura bu MVVM (Model-View-ViewModel) hisoblanadi. Unda View faqat Ekranni ko'rsatish bilan band bo'ladi, barcha logika va ma'lumotlar ustidagi amallar ViewModel'da bajariladi. ViewModel esa o'z navbatida uzoqdagi serverlar (Manager'lar) bilan gaplashadi.

Swift Concurrency chiqishi bilan bu arxitektura ancha soddalashdi. Keling, har bir qadamda qanday "Best Practice" lardan foydalanish kerakligini bosqichma-bosqich ko'rib chiqamiz.

1-qoida: ViewModel doimo @MainActor bo'lishi shart!

ViewModel'ning asosiy vazifasi – bu o'zidagi @Published o'zgaruvchilarni yangilash orqali Ekranga (View) signal jo'natishdir. Va biz juda yaxshi bilamizki, Ekrandagi har qanday o'zgarish faqat va faqat Asosiy Oqimda (Main Thread) bajarilishi kerak.

Demak, ViewModel'ning deyarli 99% ishi Asosiy Oqimda o'tadi. Shunday ekan, nega biz har bir o'zgaruvchi va funksiyaga bittalab @MainActor yozib chiqishimiz kerak? Eng oson yo'li: Butun boshli ViewModel klassini @MainActor ga aylantiring!

// Endi ushbu klass ichidagi HAKMA NARSA Asosiy Oqimda ishlaydi
@MainActor
final class ProfileViewModel: ObservableObject {
    @Published var userName: String = "Kutilmoqda..."

    // ...
}

2-qoida: View (Ekran) har doim sinxron bo'lishi kerak

Ekrandagi tugmalar (Button) yoki View funksiyalari to'g'ridan-to'g'ri async bo'lmasligi kerak. Tugma bosilganda darhol ishga tushadigan sinxron funksiya ViewModel'da yoziladi va o'sha funksiyaning ichida Task yaratiladi.

@MainActor
final class ProfileViewModel: ObservableObject {
    @Published var userName: String = "Kutilmoqda..."

    // Bu funksiya oddiy (sinxron)
    func loadDataButtonPressed() {
        // Asinxron olamga o'tish uchun Task yaratiladi
        Task {
            // ... serverdan ma'lumot olish ...
        }
    }
}

Nega biz funksiyani func load() async deb yozib, keyin Task ni View'ning o'zida yaratib (Task { await vm.load() }) ishlatmayapmiz? Chunki Task'larni boshqarish (Cancel qilish, ushlab turish) View'ning emas, aynan ViewModel'ning javobgarligi hisoblanadi!

3-qoida: Data Manager'lar Actor bo'lishi kerak

ViewModel ma'lumotlarni o'zi to'plamaydi, uni NetworkManager yoki DatabaseManager kabi alohida qatlamlardan so'rab oladi. O'sha Manager'lar bir vaqtning o'zida ko'plab ViewModel'lar tomonidan chaqirilishi mumkin. Shu sababli, Data Manager'lar albatta class emas, actor bo'lishi shart (Data Race ni oldini olish uchun).

4-qoida: Task'larni to'g'ri boshqarish

Oldingi darsda o'rganganimizdek, ViewModel'dagi har bir Task o'z-o'zidan o'chib ketmaydi. Agar foydalanuvchi orqaga qaytib ketsa (View yopilsa), ishlashda davom etayotgan Task'larni bekor qilish (cancel) kerak. Buning uchun ViewModel o'zining ichidagi Task'larni bitta massivga yig'ib boradi.

Bularning barchasini birlashtirib ko'ramiz (To'liq Namunaviy Kod)

import SwiftUI

// 1. Xavfsiz Data Manager (Actor)
actor DatabaseManager {
    func getUserName() -> String {
        // ... serverdan uzoq vaqt ma'lumot olish jarayoni ...
        return "Ali Valiyev"
    }
}

// 2. MainActor ViewModel
@MainActor
final class ProfileViewModel: ObservableObject {
    @Published var userName: String = "Yuklanmoqda..."
    private let manager = DatabaseManager()

    // Tasklarni saqlab turuvchi massiv
    private var tasks: [Task<Void, Never>] = []

    func fetchUser() {
        let task = Task {
            // manager bu boshqa Actor. Unga murojaat qilamiz (await bilan)
            let result = await manager.getUserName()

            // 💡 ENG MUHIM JOYE:
            // Ma'lumot kelgach, kod avtomatik tarzda MainActor'ga (ViewModel asliga) qaytadi!
            // Ilgari bu yerda 'await MainActor.run { }' yozish shart edi, endi shart emas.
            self.userName = result
        }
        // Taskni yig'ib borish
        tasks.append(task)
    }

    func cancelTasks() {
        tasks.forEach { $0.cancel() }
        tasks = []
    }
}

// 3. Toza va oddiy UI (View)
struct ProfileView: View {
    @StateObject private var vm = ProfileViewModel()

    var body: some View {
        VStack {
            Text(vm.userName)
            Button("Yangilash") {
                vm.fetchUser() // Oddiy, sinxron chaqiruv
            }
        }
        .onDisappear {
            vm.cancelTasks() // Ekran yopilganda barcha vazifalar to'xtatiladi
        }
    }
}

Xulosa:

  • ViewModel = @MainActor class
  • Manager = actor
  • View'dan chaqiruv = Sinxron funksiya -> ichida Task
  • Tasklarni massivga yig'ish va View yopilganda .cancel() qilish.

Bu pattern bugungi kunda iOS ilovalar qurishda eng standart, xavfsiz va toza yondashuv hisoblanadi.

Buy mea coffee