Main Thread — mobil tətbiqlərdə bütün istifadəçi interfeysini emal edən əsas icra thread-idir: toxunuşlar, çəkmə, layout yeniləməsi və animasiyalar. iOS-da bu RunLoop.main, Android-də — Looper.getMainLooper(). Bu thread-də hər hansı uzun müddətli əməliyyat UI-ni bloklayır və ANR (Android) və ya interfeysin donmasına (iOS) səbəb olur. Apple UIKit Documentation-ya görə, UI sinifləri thread təhlükəsiz deyil və yalnız Main Thread-dən çağırılmalıdır.
Əsas
Main Thread — tətbiq işə salındıqda əməliyyat sistemi tərəfindən yaradılan və bütün istifadəçi interfeysi hadisələrini emal edən thread-dir. Mobil platformalar kontekstində Main Thread həmçinin UI Thread adlanır, çünki çəkmə, toxunuşların işlənməsi və animasiya ilə bağlı bütün əməliyyatlar onun üzərində yerinə yetirilir. Hər bir tətbiqin dəqiq bir Main Thread-i var və bütün UI framework-ləri (UIKit, AppKit, Android Views, Compose UI) thread-unsafe-dir — digər thread-lərdən çağırıldıqda düzgün işə zəmanət vermirlər.
Architectural olaraq Main Thread Event Loop pattern-ni həyata keçirir: thread sonsuz olaraq yeni hadisələri (toxunuşlar, sistem bildirişləri, timer-lər) gözləyir və onları növbə sırası ilə emal edir. Bir hadisə emal edilərkən, növbəti növbədə gözləyir. Emal 100-200 millisaniyədən çox çəkərsə, istifadəçi gecikmə (jank) hiss edir. 5 saniyədən çox (Android) olarsa — sistem ANR (Application Not Responding) dialoqunu göstərir və tətbiqi bağlamağı təklif edir.
Main Thread-i anlamağın əhəmiyyətini qiymətləndirmək çətindir: mobil tətbiqlərdə performans problemlərinin 90%-nin mənbəyidir. Tərtibatçılar tez-tez ağır əməliyyatları (şəbəkə, fayllar, JSON parse, şəkil sıxma) fon thread-lərinə köçürməyi unudurlar. Emulatorda 10 millisaniyəyə yerinə yetirilən əməliyyat, real cihazda yavaş disk ilə 500 millisaniyə çəkə bilər və nəzərə çarpan laqa səbəb ola bilər.
Thread-unsafe UI framework-ləri — UIKit (2007) və Android-in (2008) ilk versiyalarından qoyulmuş memarlıq qərarıdır. Əsas səbəb performansdır: UI komponentlərinə girişin blokadalar (locks) vasitəsilə sinxronizasiyası hər çəkmə əməliyyatına əlavə yük qoyardı. Bunun əvəzinə framework-lər tələb edir ki, bütün UI dəyişiklikləri ciddi şəkildə bir thread-də yerinə yetirilsin, race condition-ları overhead olmadan aradan qaldırsın.
Təsəvvür edin ki, iki fon thread-i eyni anda textView.setText() çağırır. UI thread-safe olsaydı, hər iki çağırış mutex vasitəsilə sinxronlaşdırılardı ki, bu da çəkməni 20-40% yavaşladardı. Hazırkı memarlıqda fon thread-indən hər hansı UI çağırışı ya ignore edilir, ya da crash-ə səbəb olur (iOS-da — Main Thread Checker Exception, Android-də — CalledFromWrongThreadException). İstisna — Android-də SurfaceView və TextureView, burada çəkmə ayrıca thread-dən yerinə yetirilə bilər.
Müasir mobil framework-lər (SwiftUI, Jetpack Compose) bu məhdudiyyəti saxlayır: SwiftUI tələb edir ki, State və ObservedObject-də bütün dəyişikliklər Main Thread-də baş versin, baxmayaraq ki, çəkmənin özü qismən fon thread-lərinə çıxarılıb. Jetpack Compose də State-in Main Thread-də dəyişdirilməsini gözləyir. İstisna — drawBehind və layout ilə bağlı Compose modifier-ləri, aşkar sənədləşmə ilə digər thread-lərdən çağırıla bilər.
DispatchQueue.main — iOS-da Main Thread-ə kod göndərmək üçün əsas mexanizmdir. Bu, tətbiqin əsas RunLoop-na bağlı serial növbədir. Ora göndərilən bütün bloklar ardıcıl olaraq, gəliş sırası ilə yerinə yetirilir. SwiftUI və UIKit avtomatik yenilənir, əgər siz State-i dəyişdirirsinizsə və ya setNeedsLayout()-i Main Thread-dən çağırırsınızsa. Fon tapşırığından nəticəni asinxron qaytarmaq üçün DispatchQueue.main.async {} istifadə edin.
Objective-C-Swift körpüsündə həmçinin Thread.isMainThread mövcuddur — cari kodun əsas thread-də yerinə yetirilib-yetirilmədiyini yoxlayan xüsusiyyət. Mövcud UIKit layihələri üçün bu standart pattern-dir: if Thread.isMainThread { updateUI() } else { DispatchQueue.main.async { updateUI() } }. SwiftUI-də bu yoxlama adətən tələb olunmur, çünki framework özü body və modifier-in Main Thread-də çağırılmasını təmin edir.
import UIKit
class ViewController: UIViewController {
let imageView = UIImageView()
func loadImageFromNetwork() {
// Fon thread-i: şəklin endirilməsi
DispatchQueue.global(qos: .background).async { [weak self] in
guard let url = URL(string: "https://example.com/image.png"),
let data = try? Data(contentsOf: url),
let image = UIImage(data: data)
else { return }
// UI yeniləməsi üçün Main Thread-ə qayıt
DispatchQueue.main.async {
self?.imageView.image = image
self?.imageView.setNeedsLayout()
}
}
}
// Kodun Main Thread-də icra olunub-olunmadığını yoxlama
func safeUpdateUI() {
if Thread.isMainThread {
updateUI()
} else {
DispatchQueue.main.async {
self.updateUI()
}
}
}
private func updateUI() {
print("UI Main Thread-də yeniləndi")
}
}
loadImageFromNetwork() nümunəsi düzgün pattern-i göstərir: URLSession və ya Data(contentsOf:) DispatchQueue.global vasitəsilə fon thread-də yerinə yetirilir, sonra nəticə UIImageView-i yeniləmək üçün DispatchQueue.main-ə qaytarılır. DispatchQueue.main.async olmadan tətbiq fon thread-dən UIKit çağırarkən NSInternalInconsistencyException ilə crash olacaq.
iOS-da Main Thread-də kodun icrasının ən etibarlı yolu — açıq şəkildə DispatchQueue.main.async vasitəsilə göndərməkdir. Siz artıq Main Thread-də olsanız belə, async göndərmə problem yaratmır: GCD onu RunLoop-un növbəti iterasiyasında emal edir. Sinxron icra üçün DispatchQueue.main.sync istifadə edin, lakin bu, Main Thread-dən sync çağırarsanız deadlock-a səbəb ola bilər. Qayda: nəticənin qaytarılması üçün async, yalnız əsas thread-də olmadığınıza zəmanət varsa sync.
RunLoop.main — iOS-un əsas hadisə növbəsi ilə əlaqəli CFRunLoop obyektidir. O, giriş mənbələrini (touch events), timer-ləri və DispatchQueue.main bloklarını emal edir. Hər çəkmə kadrı (60/120 FPS) VSync-dən əvvəl RunLoop-da bütün əməliyyatların tamamlanmasını tələb edir. Main Thread-də əməliyyatlar 16.6 ms (60 FPS) və ya 8.3 ms (120 FPS) dən çox çəkərsə, tətbiq kadrları atır, bu da vizual olaraq jank və ya stutter kimi özünü göstərir.
Looper.getMainLooper() — Android-də əsas thread ilə iş üçün əsas mexanizmdir. Android-də hər Main Thread Looper-a malikdir, o, növbədən (MessageQueue) sonsuz olaraq mesajları çıxarır və emal üçün Handler-ə ötürür. Activity.runOnUiThread() və View.post() — bunlar Handler(Looper.getMainLooper()) üzərində yüksək səviyyəli sarğılardır. Dispatchers.Main ilə Kotlin Coroutines — əsas thread-ə qayıtmağın müasir yoludur.
Android həmçinin StrictMode təqdim edir — Main Thread-i bloklayan əməliyyatları aşkar etmək üçün alət. StrictMode.setThreadPolicy() sizə siyasət təyin etməyə imkan verir: əsas thread-də şəbəkə çağırışlarının qadağanı (NetworkPolicy), diskdən oxuma (DiskRead), diskə yazma (DiskWrite). Siyasət pozulduqda istisna yaradılır və ya logcat-ə mesaj yazılır.
// Android: Main Thread və Kotlin Coroutines ilə iş
import android.os.Bundle
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.URL
class MainActivity : ComponentActivity() {
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
textView = TextView(this)
setContentView(textView)
// Nümunə: asinxron məlumat yükləmə
lifecycleScope.launch {
val result = loadData() // Dispatchers.IO-da yerinə yetirilir
textView.text = result // UI Main Thread-də
}
}
private suspend fun loadData(): String {
return withContext(Dispatchers.IO) {
URL("https://api.example.com/data").readText()
}
}
}
// StrictMode Main Thread pozuntularını aşkar etmək üçün
class App : Application() {
override fun onCreate() {
super.onCreate()
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
}
}
Kotlin nümunəsi Dispatchers.Main düzgün istifadəsini lifecycleScope.launch və Dispatchers.IO-nu withContext vasitəsilə göstərir. Bütün şəbəkə işi IO-dispatcher-də yerinə yetirilir, TextView yeniləməsi isə avtomatik Main Thread-də, çünki lifecycleScope-də launch default olaraq Dispatchers.Main istifadə edir. Application.onCreate()-də StrictMode təsadüfi şəbəkə çağırışlarını və disk əməliyyatlarını əsas thread-də tutur.
Main Thread Checker — Xcode-da quraşdırılmış alət (Xcode 9-dan mövcuddur), UIKit, AppKit və digər UI framework-lərinin fon thread-lərindən çağırışlarını tapır. Debug zamanı Main Thread Checker bütün UI-API çağırışlarını analiz edir və pozuntu aşkar edildikdə ətraflı stack trace ilə breakpoint göstərir. Real cihazlarda (release quruluşunda) Main Thread Checker işləmir — pozuntular crash və ya düzgün olmayan davranış kimi özünü göstərəcək.
Android-də analoq StrictMode (yuxarıda təsvir edilmişdir) və daxili log detektordur: fon thread-indən View.setText() və ya View.invalidate() çağırıldıqda Android CalledFromWrongThreadException atır. Əlavə olaraq Android Studio Profiler Main Thread-də hansı əməliyyatların yerinə yetirildiyini göstərir. Main Thread-də şəbəkə və ya fayl əməliyyatları görürsünüzsə — bu problemin dəqiq əlamətidir.
| Alət | Platforma | Nəyi aşkar edir |
|---|---|---|
| Main Thread Checker | iOS (Xcode) | Fon thread-lərindən UIKit/AppKit çağırışları |
| StrictMode | Android | Şəbəkə, disk, Main Thread-də uzun əməliyyatlar |
| Android Studio Profiler | Android | Main Thread yükünün zamanla vizuallaşdırılması |
| Time Profiler | iOS (Instruments) | Main Thread-də metodların icra müddətinin ölçülməsi |
| HUD / DispatchQueue.main.async | iOS | Debug vasitəsilə UI bloklanmasının vizual göstəricisi |
Main Thread bloklanmasının ən nəzərə çarpan simptomu — janky scroll (kəsikli scroll). İstifadəçi UITableView və ya RecyclerView scroll etdikdə, sistem növbəti kadrın 16 ms ərzində hazır olacağını gözləyir. Main Thread-də şəkil dekodlaşdırması və ya JSON parse icra olunursa, kadr çəkmə gecikir və istifadəçi sıçrayışlar görür. Diaqnostika üçün profiler-dən istifadə edin: əgər prepareDisplay() və ya layoutSubviews() metodu >16 ms çəkirsə — məlumatlar yanlış thread-də emal olunur.
Birinci ssenari — Main Thread-də URLConnection və ya Data(contentsOf:) vasitəsilə sinxron şəbəkə sorğusu. Android-də StrictMode detectNetwork() ilə dərhal bu pozuntunu tutacaq. iOS-da sinxron URLSession açıq səhv verməyəcək, lakin UI sorğu müddəti ərzində (1-10 saniyə) donacaq. Həll: asinxron callback ilə URLSession.dataTask (iOS) və ya Retrofit/OkHttp (Android) istifadə edin.
İkinci ssenari — şəkillərin dekodlaşdırılması və sıxılması. Android-də əsas thread-də UIImage(data:) və ya BitmapFactory.decodeResource() — jank-ın ən çox yayılmış səbəblərindən biridir. 4000x3000 piksel şəkil 50-150 millisaniyə dekodlaşır ki, bu da 16 ms limitini aşır. Həll: fon thread-də dekodlaşdırmaya zəmanət verən ImageLoader (Kingfisher, Coil, Glide) istifadə edin.
Üçüncü ssenari — JSON parse. Main Thread-də JSONSerialization (iOS) və ya JSONObject (Android) vasitəsilə API cavabının təhlili. Hətta 100 KB-lıq kiçik JSON 5-15 millisaniyə parse olunur, lakin yavaş cihazlarda — 50 millisaniyəyə qədər. Digər əməliyyatlarla birlikdə bu yığılır və buraxılmış kadrlara səbəb olur. Həll: fon thread-də parse() çağırışı ilə kotlinx.serialization/Decodable istifadə edin, Main Thread-də yalnız nəticənin təyin edilməsini buraxın.
Tez-tez verilən suallar
Main Thread — tətbiqin əsas thread-i, onun üzərində bütün UI əməliyyatları yerinə yetirilir: toxunuşların işlənməsi, ekranın çəkilməsi, animasiyalar, layout yeniləməsi. iOS-da bu RunLoop.main və DispatchQueue.main, Android-də — Looper.getMainLooper(). Bütün UI framework-ləri (UIKit, Android Views) thread-unsafe-dir və yalnız Main Thread-dən çağırılma tələb edir. Bu thread-də hər hansı uzun əməliyyat interfeysi bloklayır.
UI framework-ləri memarlıq baxımından performans üçün thread-unsafe-dir: blokadalar vasitəsilə giriş sinxronizasiyası hər çəkmə əməliyyatına 20-40% əlavə yük qoyardı. UIKit və Android tərtibatçıları bir thread modelini seçdilər, burada race condition mutex olmadan aradan qaldırılır. Bütün UI dəyişiklikləri ciddi şəkildə Main Thread-də yerinə yetirilməlidir — əks halda crash və ya düzgün olmayan göstərim.
iOS-da kod göndərmək üçün DispatchQueue.main.async { } istifadə edin. Android-də — runOnUiThread { } və ya Dispatchers.Main ilə Kotlin Coroutines. Müasir yanaşma — korutinlər: fon işi üçün withContext(Dispatchers.IO) və launch-da avtomatik Dispatchers.Main. Java layihələri üçün Handler(Looper.getMainLooper()).post { }.
ANR (Application Not Responding) — Android dialoqu, Main Thread 5 saniyədən çox bloklandıqda görünür. ANR o deməkdir ki, sistem giriş hadisəsinə (toxunma, düymə basma) tətbiqdən cavab almayıb və ya BroadcastReceiver 10 saniyə ərzində tamamlanmayıb. Səbəb — Main Thread-də sinxron əməliyyat: şəbəkə sorğusu, verilənlər bazası ilə iş, mürəkkəb hesablamalar. iOS-da analoq — dialogsuz UI donması.
SwiftUI avtomatik olaraq body və modifier-in Main Thread-də yerinə yetirilməsini təmin edir. Lakin fon thread-indən @Published xüsusiyyətlərinin və ya State-in dəyişdirilməsi (məsələn, URLSession delegate-dən) problemlərə səbəb ola bilər. ObservableObject sinifləri üçün @MainActor istifadə edin ki, bütün metodları Main Thread-də yerinə yetirilsin. SwiftUI 5.5+-də @MainActor ObservableObject üçün avtomatik əlavə olunur.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun