Published on

Swiftda @MainActor

Authors

@MainActor — UI thread xavfsizligi

iOS da UI faqat main thread (asosiy oqim) da yangilanishi kerak. Bu Apple ning qat'iy qoidasi. Background thread dan UI ga tegish — ekranning qotib qolishi, noto'g'ri ko'rinish yoki ilova crash bo'lishiga olib keladi. @MainActor — bu qoidani kompilyatsiya vaqtida kafolatlaydi. Agar main thread da bo'lishi kerak bo'lgan kodni background dan chaqirsangiz — compiler xato beradi.

MainActor — Apple tomonidan yaratilgan global actor. U butun dasturda bitta va main (UI) thread da ishlaydi. @MainActor ni class, funksiya yoki property ga qo'yish mumkin.

Muammo — background dan UI yangilash

Quyida ikkita misol bor. Birinchisida @Published property background thread dan o'zgartiriladi — bu xavfli. Ikkinchisida @MainActor qo'yilgan — barcha UI o'zgarishlar avtomatik main thread da bajariladi.

// ═══════════════════════════════════════
//  ❌ XATO — background thread dan UI yangilash
// ═══════════════════════════════════════
class XavfliViewModel: ObservableObject {
    @Published var maqolalar: [String] = []

    func yuklash() {
        Task {
            // Bu kod background thread da ishlashi mumkin
            let data = try await URLSession.shared.data(
                from: URL(string: "https://api.example.com")!
            )
            // ❌ @Published ni background dan o'zgartirish
            // Swift 6 da kompilyatsiya xatosi!
            self.maqolalar = ["Yangi maqola"]
        }
    }
}


// ═══════════════════════════════════════
//  ✅ TO'G'RI — @MainActor bilan
// ═══════════════════════════════════════
@MainActor  // Butun class main thread da
class XavfsizViewModel: ObservableObject {
    @Published var maqolalar: [String] = []
    @Published var yuklanmoqda = false

    func yuklash() async {
        yuklanmoqda = true  // ✅ Main thread — xavfsiz

        // await — background ga o'tadi, natija main ga qaytadi
        do {
            let (data, _) = try await URLSession.shared.data(
                from: URL(string: "https://api.example.com")!
            )
            // ✅ Main thread ga qaytdi — xavfsiz
            maqolalar = try JSONDecoder().decode([String].self, from: data)
        } catch {
            print("Xato: \(error)")
        }

        yuklanmoqda = false  // ✅ Main thread — xavfsiz
    }
}

@MainActor qo'llash usullari

@MainActor ni uch xil tarzda qo'llash mumkin: butun class ga (barcha property va metodlar main thread da), bitta funksiya ga (faqat shu funksiya main thread da, qolganlari erkin) yoki bitta property ga (faqat shu property main thread da o'zgartiriladi).

// ═══════════════════════════════════════
//  1. BUTUN CLASS GA
// ═══════════════════════════════════════
@MainActor
class ProfilViewModel: ObservableObject {
    @Published var ism = ""      // main thread da
    @Published var rasm: UIImage? // main thread da

    func yangilash() async {     // main thread da
        ism = "Ali"
    }
}


// ═══════════════════════════════════════
//  2. FAQAT BITTA FUNKSIYAGA
// ═══════════════════════════════════════
class TarmoqXizmati {
    // Bu funksiya background da ishlaydi
    func yuklash() async throws -> [String] {
        let (data, _) = try await URLSession.shared.data(
            from: URL(string: "https://api.example.com")!
        )
        return try JSONDecoder().decode([String].self, from: data)
    }

    // Bu funksiya faqat main thread da ishlaydi
    @MainActor
    func uiYangilash(maqolalar: [String]) {
        // UI bilan ishlash — xavfsiz
        print("UI yangilandi: \(maqolalar.count) ta maqola")
    }
}


// ═══════════════════════════════════════
//  3. FAQAT BITTA PROPERTY GA
// ═══════════════════════════════════════
class AralashClass {
    @MainActor var uiMatni = ""      // faqat main thread da
    var backgroundQiymati = 0         // istalgan thread da
}

MainActor.run — qo'lda main thread ga o'tish

Ba'zan @MainActor qo'yilmagan funksiya ichidan UI ni yangilash kerak bo'ladi. MainActor.run { } bloki kodni main thread ga o'tkazadi. Bu blok tugagandan keyin kod yana avvalgi thread ga qaytadi. Bu DispatchQueue.main.async { } ning zamonaviy almashtirgichi.

// ═══════════════════════════════════════
//  MainActor.run — background dan main ga
// ═══════════════════════════════════════
func backgroundIsh() async {
    // Bu background thread da
    let natija = ogirHisoblash()

    // Main thread ga o'tish
    await MainActor.run {
        // Bu main thread da — UI yangilash xavfsiz
        print("UI yangilandi: \(natija)")
    }

    // Yana background ga qaytdi
    let boshqaNatija = boshqaOgirIsh()
}

func ogirHisoblash() -> Int { return 42 }
func boshqaOgirIsh() -> Int { return 100 }

SwiftUI da amaliy misol

SwiftUI da ObservableObject ViewModel ni @MainActor bilan belgilash eng to'g'ri usul. @Published property lar UI thread da yangilanishi shart@MainActor buni kafolatlaydi. nonisolated metodlar esa og'ir hisoblashlarni background da bajarish uchun ishlatiladi — main thread ni band qilmaydi.

import SwiftUI

// ═══════════════════════════════════════
//  SWIFTUI + @MAINACTOR VIEWMODEL
// ═══════════════════════════════════════
@MainActor
class VazifalarViewModel: ObservableObject {
    @Published var vazifalar: [String] = []
    @Published var yuklanmoqda = false
    @Published var xatoXabari: String?

    func yuklash() async {
        yuklanmoqda = true       // ✅ main thread
        xatoXabari = nil

        do {
            // await — tarmoq ishi background da
            let yangilar = try await tarmoqdanYukla()
            vazifalar = yangilar  // ✅ main thread ga qaytdi
        } catch {
            xatoXabari = error.localizedDescription
        }

        yuklanmoqda = false      // ✅ main thread
    }

    // nonisolated — bu metod background da ishlashi mumkin
    // Main thread ni band qilmaydi
    nonisolated func tarmoqdanYukla() async throws -> [String] {
        try await Task.sleep(for: .seconds(2))
        return ["Vazifa 1", "Vazifa 2", "Vazifa 3"]
    }
}

struct VazifalarKorinishi: View {
    @StateObject private var vm = VazifalarViewModel()

    var body: some View {
        List(vm.vazifalar, id: \.self) { vazifa in
            Text(vazifa)
        }
        .overlay {
            if vm.yuklanmoqda { ProgressView() }
        }
        .task { await vm.yuklash() }
    }
}

🎯 Topshiriq

@MainActor qo'yilgan HisobViewModel yarating: @Published var balans: Double. yuklash() async metodi 2 soniya kutib balansni yangilasin. nonisolated metod bilan og'ir hisoblashni background da bajaring. SwiftUI View da ProgressView va Text bilan ko'rsating.

Buy mea coffee