- Published on
Async Await'da Strong va Weak havolalar (Memory management)
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
Nega biz [weak self] ni unutib yubordik?
Agar siz ilgari Closure va Completion Handler lar bilan ishlagan bo'lsangiz, xotira muzlab qolmasligi (Memory Leak - Retain Cycle) ning oldini olish uchun har safar asinxron bloklarda [weak self] yozishga to'g'ri kelardi:
// Eski usul
func fetchData() {
NetworkManager.download { [weak self] result in
self?.data = result
}
}
Lekin Swift Concurrency (Async Await) kelgani va biz Task lardan foydalana boshlaganimizdan beri, biz hecham weak haqida o'ylamay qo'ydik. Nega shunday?
Keling, buni quyidagi kodda ko'rib chiqamiz:
class MyViewModel: ObservableObject {
@Published var data: String = ""
func updateData() {
Task {
// Bu yerda self.data deb yozsak nima bo'ladi?
self.data = await fetchNetworkData()
}
}
}
Task'dagi yashirin "Strong Reference"
Yuqoridagi Task ichida self.data orqali ViewModel'ga murojaat qilganimizda, biz avtomatik ravishda unga Strong Reference (Kuchli havola) o'rnatgan bo'lamiz. Bu nima degani?
Bu degani kompyuterga quyidagi buyruqni berganmiz: "Toki mana shu asinxron funksiya (fetchNetworkData) to'liq bajarilib bo'lmaguniga qadar, ushbu ViewModel xotiradan O'CHIRILMASIN!"
Aslida bu juda yaxshi xususiyat! Chunki uzoq kutilgan ma'lumot kelgan payti, uni saqlab olish uchun ViewModel hali ham tirik bo'ladi. Va eng muhimi: Asinxron vazifa o'z ishini tugatgandan so'ng, Task o'z-o'zidan yopiladi va Strong havolani qo'yib yuboradi. Natijada hech qanday memory leak bo'lmaydi!
Muammo nimada bo'lishi mumkin?
Agar bizning asinxron vazifamiz juda uzoq davom etsa-chi? (Masalan, 5 daqiqali videoni yuklab olish). Fokus boshqa oynaga o'tib ketsa va foydalanuvchiga bu video endi kerak bo'lmasa, ViewModel baribir xotirada 5 daqiqa (yuklash tugaguncha) keraksiz joyni egallab turaveradi. Yoki oldingi darsda ko'rganimizdek abadiy aylanish (AsyncPublisher) bo'lsa umuman xotiradan o'chmaydi.
Bu muammoni hal qilishning eski usuli yana [weak self] yozish bo'lardi:
// Asinxron blokda ham ishlatsak bo'ladi (lekin shart emas!)
Task { [weak self] in
let result = await fetchNetworkData()
self?.data = result
}
Lekin bu zamonaviy Swift'ga xos yechim emas! Zamonaviy yechim bu – havolalar bilan urishib o'tirmasdan, shunchaki Task'ning o'zini bekor qilishdir!
Yechim: Task'ni bekor qilish (Cancel)
Foydalanuvchi oynani yopganda, o'sha oyna uchun mas'ul bo'lgan barcha yuklash ishlari darhol to'xtatilishi eng to'g'ri strategiya hisoblanadi.
Buning uchun biz Task'ni o'zgaruvchida saqlab qolamiz:
class MyViewModel: ObservableObject {
@Published var data: String = ""
// Task'ni saqlab turish uchun o'zgaruvchi
private var myTask: Task<Void, Never>? = nil
func startDownloading() {
// Yangi Task boshlash va uni saqlash
myTask = Task {
self.data = await fetchNetworkData()
}
}
// Oyna yopilganda shu funksiyani chaqiramiz
func cancelTasks() {
myTask?.cancel()
myTask = nil
}
}
Agar bitta klassda juda ko'p Task'lar ishlayotgan bo'lsa, ularni bitta massivda ([Task]) yig'ib borish va birdaniga sikl orqali barchasini to'xtatish (cancel qilish) mumkin.
Eng oson yo'li (SwiftUI uchun)
Agar siz operatsiyalarni to'g'ridan-to'g'ri SwiftUI ekrani yuklanganda boshlasangiz, Task saqlab o'tirishga umuman hojat yo'q. Shunchaki .task modifier'idan foydalaning:
struct MyView: View {
@StateObject var vm = MyViewModel()
var body: some View {
Text(vm.data)
.task {
// Ekran ochilganda bu avtomatik ishlaydi
await vm.startDownloading()
// Foydalanuvchi ekrandan chiqib ketishi bilanoq,
// bu erdagi barcha operatsiyalar avtomatik "cancel" qilinadi!
}
}
}
Xulosa: Swift Concurrency'da xotira muammolari ehtimoli oldingisiga qaraganda ancha kam. Agar sizning vazifalaringiz faqat ma'lumot qabul qilib tugaydigan bo'lsa, xotira o'zini-o'zi tozalaydi. Agar uzoq davom etadigan jarayonlar (obunalar, taymerlar, yuklashlar) bo'lsa, eng yaxshi yo'li obyektdan weak self yasash emas, balki to'g'ridan-to'g'ri Task'ning ishlashini cancel() orqali butunlay to'xtatishdir.