- Published on
Background Threads (Fon oqimlari) va Queues (Navbatlar) bilan ishlash
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
Ilovangiz ichida sahna ortida vazifalarni bajarishimiz mumkin boʻlgan bir nechta turli oqimlar (threads) mavjud[cite: 2]. Agar siz boshqacha koʻrsatmagan boʻlsangiz, siz yozadigan deyarli barcha kodlar standart boʻyicha Main Thread (Asosiy oqim, yaʼni Thread 1) ichida ishlaydi[cite: 2].
Asosiy oqim koʻplab vazifalarni bajara oladi, biroq murakkabroq ilovalar yaratishni boshlaganingizda, unda juda koʻp ish bajarilishiga toʻgʻri keladi[cite: 2]. Agar u juda yuklanib ketsa, ilovangiz sekinlashishi, qotib qolishi (freeze) va hatto crash boʻlishi mumkin[cite: 2]. Professional dasturchi sifatida biz internetdan maʼlumot, rasm yoki videolarni yuklash kabi ogʻir vazifalarni fondagi oqimga (Background Thread) oʻtkazamiz[cite: 2].
Oqimlar bilan ishlashda eng muhim birinchi qoida: UI (foydalanuvchi interfeysi)ni, yaʼni ekranni yangilaydigan har qanday amal faqat Main Thread ichida bajarilishi shart[cite: 2]. Biz fondagi oqimga oʻtib vazifalarni bajaramiz, biroq ekranni yangilashdan aynan oldin yana asosiy oqimga qaytamiz[cite: 2].
1. Kodning Boshlangʻich Holati va Muammo
Dastlab interfeys va maʼlumotlarni yuklaydigan View Model qismini tayyorlaymiz[cite: 2].
import SwiftUI
class BackgroundThreadViewModel: ObservableObject {
@Published var dataArray: [String] = []
func fetchData() {
let newData = downloadData()
self.dataArray = newData
}
// Tarmoqdan yuklashni simulyatsiya qiluvchi og'irroq amal
private func downloadData() -> [String] {
var data: [String] = []
for x in 0..<100 {
data.append("\(x)")
print(data) // Har bir siklda chop etish
}
return data
}
}
struct BackgroundThreadBootcamp: View {
@StateObject var vm = BackgroundThreadViewModel()
var body: some View {
ScrollView {
VStack(spacing: 10) {
Text("Load Data")
.font(.largeTitle)
.fontWeight(.semibold)
.onTapGesture {
vm.fetchData()
}
ForEach(vm.dataArray, id: \.self) { item in
Text(item)
.font(.headline)
.foregroundColor(.red)
}
}
}
}
}
Xcode Debug Navigator (CPU paneli):
Xcode-ning chap tomonidagi navigator panellarida purkagich (spray bottle) belgisiga oʻxshash Debug Navigator tugmasi joylashgan. Ichidagi CPU boʻlimiga kirsangiz, ilovangiz qancha hisoblash quvvatidan foydalanayotganini koʻrsatadigan kichik datchikni koʻrasiz.
Agar bu bar qizil boʻlib, foizlar juda yuqorilab ketsa, ilova sekinlashadi va qotib qoladi. Panelning pastki qismidagi Thread boʻlimida Thread 1 (Main Thread) turaveradi. Biz yuqoridagi kodda "Load Data" tugmasini bosganimizda va ekranni aylantirganimizda, barcha jarayonlar faqat Thread 1 ichida sodir boʻladi va boshqa oqimlarda (masalan, Thread 4) hech qanday oʻzgarish koʻrinmaydi.
2. Vazifani Background Thread-ga oʻtkazish
Apple bizga foydalanish uchun bir qancha tayyor fon oqimlarini beradi. Biz DispatchQueue.global().async orqali kodimizni fon oqimiga joylashtiramiz.
func fetchData() {
// Fon oqimini ochish
DispatchQueue.global().async {
let newData = self.downloadData()
// ⚠️ DIQQAT: Bu yerda UI o'zgaruvchisini yangilash xatolikka sabab bo'ladi!
self.dataArray = newData
}
}
self.haqida qisqa eslatma:asyncyopilishi (closure) ichida kod boshqa oqimda ishlayotgani sababli, biz ushbu klassga kuchli havola (strong reference) yaratish uchunself.soʻzini yozishimiz shart.
Ilovani shu holatda yurgizib, tugmani bossak, protsess Thread 3 yoki Thread 11 kabi fon oqimlariga oʻtganini va asosiy oqim biroz yengillashganini koʻramiz.
Purple Error (Binafsharang ogohlantirish):
Biroq, Xcode-da qizil yoki binafsharang xatolik paydo boʻladi: "Publishing changes from background threads is not allowed" (Fon oqimidan turib oʻzgarishlarni eʼlon qilishga ruxsat berilmaydi). Chunki dataArray oʻzgarganda u toʻgʻridan-toʻgʻri ekrandagi View-ni yangilashga harakat qiladi, UI esa fonda yangilanishi mumkin emas.
3. Main Thread-ga qaytish (Toʻgʻri Yechim)
Maʼlumotni fonda tayyorlab boʻlgach, UI-ni yangilashdan oldin DispatchQueue.main.async orqali asosiy oqimga (Thread 1) qaytamiz.
func fetchData() {
// Fon oqimida og'ir ma'lumotni yuklash
DispatchQueue.global().async {
let newData = self.downloadData()
// UI-ni yangilash uchun asosiy oqimga qaytish
DispatchQueue.main.async {
self.dataArray = newData
}
}
}
Bunda maʼlumot fonda yuklanadi va faqat yangilanish qismi Thread 1ga yuklanadi, natijada binafsharang xatolik yoʻqoladi.
4. Quality of Service (QoS) darajalari
DispatchQueue.global() chaqirilganda, unga Quality of Service (QoS) berish orqali fon navbatlarining ustuvorlik (prioritet) darajasini belgilash mumkin.
.background: Foydalanuvchi umuman bilishi shart boʻlmagan, orqa fondagi tozalash yoki texnik xizmat koʻrsatish vazifalari uchun moʻljallangan..utility: Foydalanuvchi faol ravishda kuzatib bormaydigan, lekin ilova bajarishi kerak boʻlgan funksiyalar uchun ishlatiladi..userInitiated: Foydalanuvchi ilovani faol ishlatishiga toʻsqinlik qilishi mumkin boʻlgan, yuklanishi juda muhim boʻlgan uzoq vazifalar uchun qoʻllaniladi (ehtiyotkorlik bilan ishlatish lozim)..userInteractive: Animatsiyalar, hodisalarni qayta ishlash yoki UI-ni toʻgʻridan-toʻgʻri yangilash kabi eng yuqori ustuvorlikdagi vazifalar uchun ishlatiladi.
// Prioritetni o'zgartirib ishlatish:
DispatchQueue.global(qos: .background).async {
let newData = self.downloadData()
DispatchQueue.main.async {
self.dataArray = newData
}
}
5. Qaysi Oqimdaligini Tekshirish (Debugging)
Kodingiz ayni damda qaysi oqimda ishlayotganini aniqlash uchun Thread klassining maxsus metodlaridan foydalansangiz boʻladi:
// joriy oqim asosiy oqimmi yoki yo'qligini tekshirish (true/false qaytaradi)
print(Thread.isMainThread)
// joriy oqimning to'liq ma'lumotini (raqami bilan) chop etish
print(Thread.current)
Amaliy sinov tekshiruvi:
func fetchData() {
// Check 1
DispatchQueue.global(qos: .background).async {
print("Check 1 - isMainThread: \(Thread.isMainThread)") // false qaytaradi
print("Check 1 - current: \(Thread.current)") // Masalan: Thread number 4
let newData = self.downloadData()
// Check 2
DispatchQueue.main.async {
print("Check 2 - isMainThread: \(Thread.isMainThread)") // true qaytaradi
print("Check 2 - current: \(Thread.current)") // Thread number 1
self.dataArray = newData
}
}
}