Published on

Swiftda actor

Authors

Actor — data race himoyasi

Ko'p vazifa bir xil ma'lumotni bir vaqtda o'zgartirganda — data race paydo bo'ladi. Bu dasturda kutilmagan xatolar, noto'g'ri natijalar va crash larga olib keladi. Actor bu muammoni hal qiladi — u reference type bo'lib (class ga o'xshash), lekin o'z ma'lumotlariga bir vaqtda faqat bitta vazifa kirishi mumkin. Boshqa vazifalar navbatda kutadi.

Actor — Swift 5.5 da kiritilgan yangi tur. U class kabi ishlaydi, lekin o'z property va metodlarini izolyatsiya qiladi. Tashqaridan actor ning property yoki metodiga murojaat qilganda await yozish kerak — bu navbatda kutish degani.

Data Race muammosi

Quyida ikkita misol bor. Birinchisida oddiy class ishlatiladi — 1000 ta parallel vazifa bir xil property ni o'zgartiradi va natija har safar boshqacha bo'ladi (data race). Ikkinchisida actor ishlatiladi — natija har doim to'g'ri.

// ═══════════════════════════════════════
//  ❌ DATA RACE — class bilan
// ═══════════════════════════════════════
class XavfliHisoblagich {
    var son = 0
}

let hisoblagich = XavfliHisoblagich()

// 1000 ta parallel vazifa — bir xil property ni o'zgartiradi
for _ in 0..<1000 {
    Task {
        hisoblagich.son += 1  // ❌ DATA RACE!
        // Ikki task bir vaqtda son = 500 ni o'qiydi
        // Ikkalasi ham son = 501 yozadi
        // Bitta oshirish yo'qoldi!
    }
}

// Natija: 1000 emas, har safar boshqacha son!
// Bu kutilmagan xatolar va crashlarga olib keladi


// ═══════════════════════════════════════
//  ✅ ACTOR BILAN — data race yo'q
// ═══════════════════════════════════════
actor XavfsizHisoblagich {
    var son = 0

    // Actor ichida — sinxron kirish
    func oshir() {
        son += 1  // ✅ Xavfsiz — faqat bitta vazifa kiradi
    }

    func qiymat() -> Int {
        return son
    }
}

let xavfsiz = XavfsizHisoblagich()

for _ in 0..<1000 {
    Task {
        await xavfsiz.oshir()  // await — navbat kutadi
    }
}
// Natija: har doim 1000 ✅

Actor ishlash printsipi

Actor ning ichidagi funksiyalar izolyatsiya ichida ishlaydi. Bir vaqtda faqat bitta vazifa actor ning ichiga kira oladi — qolganlari navbatda kutadi. Actor dan tashqarida esa barcha chaqiruvlar await bilan amalga oshiriladi. let bilan e'lon qilingan property larga esa await siz ham kirish mumkin — chunki ular o'zgarmaydi va xavfsiz.

actor BankHisobi {
    let egasi: String
    private(set) var balans: Double

    init(egasi: String, balans: Double) {
        self.egasi = egasi
        self.balans = balans
    }

    // Actor ichidagi funksiyalar — izolyatsiya ichida
    // Bir vaqtda faqat bitta vazifa chaqira oladi
    func kiritish(summa: Double) {
        balans += summa
        print("\(egasi): +\(summa), balans: \(balans)")
    }

    func yechish(summa: Double) -> Bool {
        guard balans >= summa else {
            print("\(egasi): mablag' yetarli emas")
            return false
        }
        balans -= summa
        print("\(egasi): -\(summa), balans: \(balans)")
        return true
    }
}

// Tashqaridan — await bilan
let hisob = BankHisobi(egasi: "Ali", balans: 1000)

Task {
    await hisob.kiritish(summa: 500)       // await kerak!
    let balans = await hisob.balans         // property ham await
    let natija = await hisob.yechish(summa: 200)
}

// Actor diagrammasi:
// Task 1 ─────►│         │
//              │  ACTOR  │ ← faqat bitta kiradi
// Task 2 ─wait─►│ (navbat)│
//              │         │
// Task 3 ─wait─►│         │

nonisolated — izolyatsiyadan chiqarish

Ba'zan actor ichidagi metod yoki property ni izolyatsiyadan chiqarish kerak bo'ladi. Masalan, faqat let property larni ishlatadigan metod uchun izolyatsiya ortiqcha. nonisolated kalit so'zi shu maqsadda ishlatiladi — bunday metod tashqaridan await siz chaqirilishi mumkin. Lekin nonisolated metod actor ichidagi var property larga kira olmaydi.

actor Foydalanuvchi {
    let id: UUID           // let — o'zgarmaydi, izolyatsiya kerak emas
    let ism: String        // let — o'zgarmaydi
    var yosh: Int          // var — izolyatsiya kerak

    init(id: UUID, ism: String, yosh: Int) {
        self.id = id
        self.ism = ism
        self.yosh = yosh
    }

    // nonisolated — bu funksiya izolyatsiya tashqarida
    // Faqat let property larga kirishi mumkin
    // Tashqaridan await siz chaqirish mumkin
    nonisolated func tavsif() -> String {
        return "\(ism) (ID: \(id))"
        // yosh ga kira olmaydi — var bo'lgani uchun
    }

    // Oddiy metod — izolyatsiya ichida
    func tugKunniNishonla() {
        yosh += 1  // var — izolyatsiya kerak
    }
}

let user = Foydalanuvchi(id: UUID(), ism: "Ali", yosh: 25)

// nonisolated — await kerak emas
let tavsif = user.tavsif()  // ✅ sinxron

// Oddiy property/metod — await kerak
let yosh = await user.yosh  // await kerak
await user.tugKunniNishonla()

Actor va Protocol

Actor ham protocol ga mos kelishi mumkin. Lekin protokol metodlari actor izolyatsiyasiga moslashishi kerak — shuning uchun protokoldagi metodlar async deb belgilanishi kerak.

// Actor protokolga mos kelishi mumkin
protocol Saqlash {
    func saqlash(_ matn: String) async
    func oqish() async -> String?
}

actor FaylSaqlash: Saqlash {
    private var mazmun: String?

    func saqlash(_ matn: String) {
        mazmun = matn
    }

    func oqish() -> String? {
        return mazmun
    }
}

🎯 Topshiriq

actor Kesh yarating: private var saqlangan: [String: String]. saqlash(kalit:qiymat:) va olish(kalit:) metodlari bo'lsin. 100 ta parallel Task dan bir xil kalitga yozish/o'qish qiling. Actor tufayli data race bo'lmasligini ko'ring.

Buy mea coffee