- Published on
Actor'lar bilan tanishuv va 'nonisolated' kalit so'zi
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
Actor o'zi qanday muammoni hal qiladi?
Oldingi darsimizda Class (Reference Type) larning Heap xotirada joylashishini va ularga barcha oqimlar (Threadlar) orqali yagona havola qilinishini o'rgandik. Tasavvur qiling, sizda bitta ma'lumotlar ombori (Class) bor. Va birdaniga ikkita turli xil oqim (Thread A va Thread B) o'sha bitta klass ichidagi bitta arrayga ma'lumot yozmoqchi bo'ldi.
Bu holat dasturlashda Data Race (ma'lumotlar to'qnashuvi) deb ataladi. Agar ikkita thread bitta xotira nuqtasiga bir vaqtning o'zida yozmoqchi bo'lsa, tizim nima qilishni bilmaydi va dasturingiz 100% crash bo'ladi (qulaydi).
1. Muammoning eski yechimi (DispatchQueue)
Swift Concurrency paydo bo'lgunga qadar, dasturchilar bu muammoni qo'lda boshqarar edilar. Ular klass ichida maxsus navbat (Queue / Lock) yaratib, barcha amallarni o'sha navbat orqali ketma-ket bajarishga majbur qilishardi:
class MyDataManager {
// Maxsus navbat (Lock) yaratildi
private let queue = DispatchQueue(label: "com.myapp.dataQueue")
var data: [String] = []
func getRandomData(completion: @escaping (String) -> Void) {
// Hamma amallar navbat ichida bajarilishi shart
queue.async {
self.data.append(UUID().uuidString)
completion(self.data.randomElement() ?? "")
}
}
}
Bunday kodni yozish nafaqat qiyin, balki xato qilish ehtimoli ham juda yuqori edi (masalan, navbatga qo'yish esdan chiqib ketsa, ilova yana qulardi).
2. Zamonaviy yechim: Actor!
Actor - bu shunchaki o'z-o'zidan Thread-Safe bo'lgan Class'dir. Agar siz class so'zini actor ga almashtirsangiz, yuqoridagi barcha qiyin kodlar va navbatlarni (queue) tizimning o'zi avtomatik boshqaradi!
actor MyActorDataManager {
var data: [String] = []
func getRandomData() -> String {
// Hech qanday queue yoki lock shart emas!
data.append(UUID().uuidString)
return data.randomElement() ?? ""
}
}
Actor qanday ishlaydi? Actor o'zining ichidagi barcha ma'lumotlarni izolyatsiya (himoya) qiladi. Tashqaridan unga kimdir murojaat qilmoqchi bo'lsa, u avtomatik ravishda navbatga (Queue) turishini talab qiladi.
Shu sababli, actor ichidagi funksiyaga chaqiruv berganda siz doimo await so'zini yozishingiz shart. Chunki actor o'sha lahzada band bo'lishi mumkin va siz navbatingiz kelgunicha "kutib turishingiz" (await) kerak:
Task {
let manager = MyActorDataManager()
// Actor ichiga kirish uchun ruxsat (navbat) kutilmoqda...
let newData = await manager.getRandomData()
}
3. 'nonisolated' kalit so'zi nima?
Ba'zan Actor ichida shunday funksiyalar yoki o'zgaruvchilar bo'lishi mumkinki, ular asinxron emas va Actor'ning himoyalangan o'zgaruvchilariga tegmaydi (masalan, shunchaki har safar har xil matn qaytaruvchi mustaqil funksiya).
Ushbu funksiya har gal chaqirilganida await bilan navbat kutib turmaslik uchun biz nonisolated (izolyatsiyadan tashqari) kalit so'zidan foydalanamiz:
actor MyActorDataManager {
var data: [String] = []
// Izolyatsiya qilingan (xavfsiz) funksiya
func getRandomData() -> String {
data.append(UUID().uuidString)
return data.randomElement() ?? ""
}
// Izolyatsiya qilinmagan (mustaqil) funksiya
nonisolated func getOfflineData() -> String {
return "Siz oflayn rejimdasiz"
}
}
Endi bu funksiyani chaqirganimizda:
Task {
let manager = MyActorDataManager()
// Bunga kirish uchun navbat kerak -> await
let data = await manager.getRandomData()
// Bunga navbat kerak emas, darhol ishlaydi -> await shart emas
let offlineData = manager.getOfflineData()
}
Esda tuting: nonisolated funksiyalar ichidan Actor'ning boshqa izolyatsiya qilingan (himoyalangan) o'zgaruvchilariga bevosita murojaat qila olmaysiz! Ular to'liq mustaqil ishlashi kerak.