Main Thread — mobil uygulamalardaki ana yürütme iş parçacığıdır ve tüm kullanıcı arayüzünü işler: dokunmalar, işleme, düzen güncellemeleri ve animasyonlar. iOS'ta bu RunLoop.main, Android'de — Looper.getMainLooper()'dır. Bu iş parçacığındaki herhangi bir uzun süreli işlem UI'yı bloke eder ve ANR'ye (Android) veya arayüz donmasına (iOS) neden olur. Apple UIKit Belgeleri'ne göre, UI sınıfları iş parçacığı güvenli değildir ve yalnızca Main Thread'den çağrı gerektirir.
Önemli Noktalar
Main Thread, uygulama başlatıldığında işletim sistemi tarafından oluşturulan ve tüm kullanıcı arayüzü olaylarını işlemekten sorumlu olan iş parçacığıdır. Mobil platformlar bağlamında, Main Thread aynı zamanda UI Thread olarak da adlandırılır, çünkü işleme, dokunma işleme ve animasyonlarla ilgili tüm işlemler burada yürütülür. Her uygulamanın tam olarak bir Main Thread'i vardır ve tüm UI çerçeveleri (UIKit, AppKit, Android Views, Compose UI) iş parçacığı güvenli değildir — diğer iş parçacıklarından çağrıldıklarında doğru çalışmayı garanti etmezler.
Mimari olarak, Main Thread Event Loop desenini uygular: iş parçacığı yeni olayları (dokunmalar, sistem bildirimleri, zamanlayıcılar) sonsuza kadar bekler ve bunları sıra düzeninde işler. Bir olay işlenirken, sonraki sırada bekler. İşleme 100-200 milisaniyeden uzun sürerse, kullanıcı bir gecikme (jank) fark eder. 5 saniyeden (Android) uzun sürerse — sistem bir ANR (Application Not Responding) diyaloğu gösterir ve uygulamayı kapatmayı önerir.
Main Thread'i anlamanın önemi abartılamaz: mobil uygulamalardaki performans sorunlarının %90'ının kaynağıdır. Geliştiriciler genellikle ağır işlemleri (ağ, dosyalar, JSON ayrıştırma, resim sıkıştırma) arka plan iş parçacıklarına taşımayı unuturlar. Bir öykünücüde 10 milisaniye süren bir işlem, yavaş diskli gerçek bir cihazda 500 milisaniye sürebilir ve gözle görülür gecikmeye neden olabilir.
İş parçacığı güvenli olmayan UI çerçeveleri, UIKit (2007) ve Android'in (2008) ilk sürümlerinde alınan mimari bir karardır. Ana neden performanstır: kilitler (locks) aracılığıyla UI bileşenlerine erişimi senkronize etmek, her işleme işlemine ek yük getirir. Bunun yerine, çerçeveler tüm UI değişikliklerinin kesinlikle tek bir iş parçacığında gerçekleştirilmesini gerektirir ve ek yük olmadan yarış koşullarını (race conditions) ortadan kaldırır.
İki arka plan iş parçacığının aynı anda textView.setText() çağırdığını hayal edin. UI iş parçacığı güvenli olsaydı, her iki çağrı da bir mutex aracılığıyla senkronize olur ve işlemeyi %20-40 yavaşlatırdı. Mevcut mimaride, bir arka plan iş parçacığından yapılan herhangi bir UI çağrısı ya yok sayılır ya da çökmeye neden olur (iOS'ta — Main Thread Checker Exception, Android'de — CalledFromWrongThreadException). İstisna, Android'de işlemenin ayrı bir iş parçacığından gerçekleştirilebildiği SurfaceView ve TextureView'dır.
Modern mobil çerçeveler (SwiftUI, Jetpack Compose) bu sınırlamayı sürdürür: SwiftUI, State ve ObservedObject'teki tüm değişikliklerin Main Thread'de gerçekleşmesini gerektirir, ancak işlemenin kendisi kısmen arka plan iş parçacıklarına aktarılır. Jetpack Compose da Main Thread'de State değişikliği bekler. İstisna, açıkça belgelendiğinde diğer iş parçacıklarından çağrılabilen drawBehind ve layout ile ilgili Compose değiştiricileridir.
DispatchQueue.main — iOS'ta Main Thread'e kod göndermek için birincil mekanizmadır. Uygulamanın ana RunLoop'una bağlı seri bir kuyruktur. Kendisine gönderilen tüm bloklar, varış sırasına göre sırayla yürütülür. Main Thread'den State'i değiştirirseniz veya setNeedsLayout()'u çağırırsanız SwiftUI ve UIKit otomatik olarak güncellenir. Bir arka plan görevinden sonuçların eşzamansız olarak döndürülmesi için DispatchQueue.main.async {} kullanın.
Objective-C-Swift köprüsünde, Thread.isMainThread da mevcuttur — geçerli kodun ana iş parçacığında yürütülüp yürütülmediğini kontrol eden bir özelliktir. Mevcut UIKit projeleri için bu standart bir desendir: if Thread.isMainThread { updateUI() } else { DispatchQueue.main.async { updateUI() } }. SwiftUI'de bu kontrol genellikle gerekli değildir, çünkü çerçeve body ve modifier'ın Main Thread'de yürütülmesini garanti eder.
import UIKit
class ViewController: UIViewController {
let imageView = UIImageView()
func loadImageFromNetwork() {
// Arka plan iş parçacığı: resim indiriliyor
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'yı güncellemek için Main Thread'e dön
DispatchQueue.main.async {
self?.imageView.image = image
self?.imageView.setNeedsLayout()
}
}
}
// Kodun Main Thread'de yürütülüp yürütülmediğini kontrol et
func safeUpdateUI() {
if Thread.isMainThread {
updateUI()
} else {
DispatchQueue.main.async {
self.updateUI()
}
}
}
private func updateUI() {
print("Main Thread'de UI güncellendi")
}
}
Örnekte, loadImageFromNetwork() doğru deseni gösterir: URLSession veya Data(contentsOf:) DispatchQueue.global aracılığıyla bir arka plan iş parçacığında yürütülür, ardından sonuç UIImageView'i güncellemek için DispatchQueue.main'e döndürülür. DispatchQueue.main.async olmadan, uygulama bir arka plan iş parçacığından UIKit'i çağırırken NSInternalInconsistencyException ile çökecektir.
iOS'ta Main Thread'de kod yürütmenin en güvenilir yolu, DispatchQueue.main.async aracılığıyla açık göndermedir. Zaten Main Thread'de olsanız bile, async gönderme soruna neden olmaz: GCD bunu bir sonraki RunLoop yinelemesinde işler. Senkron yürütme için DispatchQueue.main.sync kullanın, ancak bu Main Thread'den çağrılırsa kilitlenmeye neden olabilir. Kural: sonuçları döndürmek için async, yalnızca ana iş parçacığında olmadığınızdan eminseniz sync.
RunLoop.main, iOS'un ana olay kuyruğuyla ilişkili bir CFRunLoop nesnesidir. Giriş kaynaklarını (dokunma olayları), zamanlayıcıları ve DispatchQueue.main bloklarını işler. Her işleme karesi (60/120 FPS), dikey senkronizasyon darbesinden (VSync) önce RunLoop'daki tüm işlemlerin tamamlanmasını gerektirir. Main Thread'deki işlemler 16,6 ms'den (60 FPS) veya 8,3 ms'den (120 FPS) uzun sürerse, uygulama kareleri düşürür ve görsel olarak jank veya stutter olarak ortaya çıkar.
Looper.getMainLooper() — Android'de ana iş parçacığıyla çalışmak için ana mekanizmadır. Android'deki her Main Thread, kuyruktan (MessageQueue) sonsuza kadar mesaj çıkaran ve bunları işlenmek üzere bir Handler'a ileten bir Looper'a sahiptir. Activity.runOnUiThread() ve View.post(), Handler(Looper.getMainLooper()) etrafındaki üst düzey sarmalayıcılardır. Dispatchers.Main ile Kotlin Coroutines, ana iş parçacığına dönmenin modern yoludur.
Android ayrıca StrictMode sağlar — Main Thread'i bloke eden işlemleri tespit etmek için bir araç. StrictMode.setThreadPolicy() bir politika belirlemenize olanak tanır: ana iş parçacığında ağ çağrılarının (NetworkPolicy), disk okumalarının (DiskRead), disk yazmalarının (DiskWrite) yasaklanması. Bir politika ihlal edildiğinde, bir istisna oluşturulur veya logcat'e bir mesaj yazılır.
// Android: Main Thread ve Kotlin Coroutines ile Çalışma
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)
// Örnek: Eşzamansız Veri Yükleme
lifecycleScope.launch {
val result = loadData() // Dispatchers.IO'da yürütülüyor
textView.text = result // Main Thread'de UI
}
}
private suspend fun loadData(): String {
return withContext(Dispatchers.IO) {
URL("https://api.example.com/data").readText()
}
}
}
// Main Thread İhlallerini Tespit Etmek İçin StrictMode
class App : Application() {
override fun onCreate() {
super.onCreate()
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
}
}
Kotlin örneği, lifecycleScope.launch aracılığıyla Dispatchers.Main'in ve withContext aracılığıyla Dispatchers.IO'nun doğru kullanımını gösterir. Tüm ağ çalışması IO dağıtıcısında gerçekleştirilirken, TextView güncellemesi, lifecycleScope'teki launch varsayılan olarak Dispatchers.Main kullandığı için otomatik olarak Main Thread'de gerçekleşir. Application.onCreate() içindeki StrictMode, ana iş parçacığındaki yanlışlıkla ağ çağrılarını ve disk işlemlerini durdurur.
Main Thread Checker — Xcode'da yerleşik bir araçtır (Xcode 9'dan beri mevcuttur) ve arka plan iş parçacıklarından UIKit, AppKit ve diğer UI çerçevelerine yapılan çağrıları tespit eder. Hata ayıklama sırasında, Main Thread Checker tüm UI-API çağrılarını analiz eder ve bir ihlal tespit ettiğinde ayrıntılı bir yığın iziyle birlikte bir kesme noktası gösterir. Gerçek cihazlarda (sürüm yapılarında), Main Thread Checker çalışmaz — ihlaller çökme veya yanlış davranış olarak ortaya çıkar.
Android'de eşdeğeri StrictMode'dur (yukarıda açıklanmıştır) ve yerleşik günlük dedektörüdür: bir arka plan iş parçacığından View.setText() veya View.invalidate() çağrıldığında, Android CalledFromWrongThreadException fırlatır. Ek olarak, Android Studio Profiler Main Thread'de hangi işlemlerin yürütüldüğünü gösterir. Main Thread'de ağ veya dosya işlemleri görüyorsanız — bu bir sorunun açık işaretidir.
| Araç | Platform | Ne Tespit Eder |
|---|---|---|
| Main Thread Checker | iOS (Xcode) | Arka plan iş parçacıklarından UIKit/AppKit çağrıları |
| StrictMode | Android | Main Thread'de ağ, disk, uzun işlemler |
| Android Studio Profiler | Android | Zaman içinde Main Thread yükünün görselleştirilmesi |
| Time Profiler | iOS (Instruments) | Main Thread'de yöntem yürütme süresinin ölçümü |
| HUD / DispatchQueue.main.async | iOS | Hata ayıklama yoluyla UI bloke etmenin görsel göstergesi |
Main Thread bloke etmenin en belirgin belirtisi sarsıntılı kaydırmadır (janky scroll). Bir kullanıcı UITableView veya RecyclerView'ı kaydırdığında, sistem bir sonraki karenin 16 ms içinde hazır olmasını bekler. Main Thread'de resim kod çözme veya JSON ayrıştırma yapılıyorsa, kare işleme gecikir ve kullanıcı takılmalar görür. Teşhis için bir profil oluşturucu kullanın: prepareDisplay() veya layoutSubviews() >16 ms sürüyorsa — veriler yanlış iş parçacığında işleniyor demektir.
İlk senaryo — Main Thread'de URLConnection veya Data(contentsOf:) aracılığıyla senkron ağ isteği. Android'de, detectNetwork() ile StrictMode bu ihlali hemen yakalar. iOS'ta, senkron bir URLSession açık bir hata vermez, ancak istek sırasında (1-10 saniye) UI donar. Çözüm: eşzamansız geri aramalı URLSession.dataTask (iOS) veya Retrofit/OkHttp (Android) kullanın.
İkinci senaryo — resim kod çözme ve sıkıştırma. Android'de ana iş parçacığında UIImage(data:) veya BitmapFactory.decodeResource() jank'ın en yaygın nedenlerinden biridir. 4000x3000 piksel bir resim 50-150 milisaniyede çözülür ve 16 ms sınırını aşar. Çözüm: arka plan iş parçacığında kod çözmeyi garanti eden ImageLoader (Kingfisher, Coil, Glide) kullanın.
Üçüncü senaryo — JSON ayrıştırma. Main Thread'de JSONSerialization (iOS) veya JSONObject (Android) aracılığıyla API yanıtını ayrıştırma. Küçük bir 100 KB JSON bile 5-15 milisaniyede ayrıştırılır, ancak yavaş cihazlarda — 50 milisaniyeye kadar. Diğer işlemlerle birleştiğinde bu birikir ve kare düşüşlerine yol açar. Çözüm: parse()'ı arka plan iş parçacığında çağrılan kotlinx.serialization/Decodable kullanın ve Main Thread'de yalnızca sonuç atamasını bırakın.
Sıkça Sorulan Sorular
Main Thread, tüm UI işlemlerinin (dokunma işleme, ekran işleme, animasyonlar, düzen güncellemeleri) gerçekleştirildiği uygulamanın ana iş parçacığıdır. iOS'ta bu RunLoop.main ve DispatchQueue.main, Android'de — Looper.getMainLooper()'dır. Tüm UI çerçeveleri (UIKit, Android Views) iş parçacığı güvenli değildir ve yalnızca Main Thread'den çağrı gerektirir. Bu iş parçacığındaki herhangi bir uzun süreli işlem arayüzü bloke eder.
UI çerçeveleri performans için mimari olarak iş parçacığı güvenli değildir: kilitler aracılığıyla erişimi senkronize etmek, her işleme işlemine %20-40 ek yük ekler. UIKit ve Android geliştiricileri, mutex olmadan yarış koşullarının ortadan kaldırıldığı tek iş parçacıklı bir model seçti. Tüm UI değişiklikleri kesinlikle Main Thread'de gerçekleştirilmelidir — aksi takdirde çökme veya yanlış görüntüleme.
iOS'ta ana kuyruğa kod göndermek için DispatchQueue.main.async { } kullanın. Android'de — runOnUiThread { } veya Dispatchers.Main ile Kotlin Coroutines. Modern yaklaşım coroutine'lerdir: arka plan çalışması için withContext(Dispatchers.IO) ve launch'te otomatik Dispatchers.Main. Java projeleri için Handler(Looper.getMainLooper()).post { }.
ANR (Application Not Responding), Main Thread 5 saniyeden fazla bloke edilirse görünen bir Android diyaloğudur. ANR, sistemin uygulamadan bir giriş olayına (dokunma, tuşa basma) yanıt almadığı veya bir BroadcastReceiver'ın 10 saniye içinde tamamlanmadığı anlamına gelir. Neden, Main Thread'deki senkron bir işlemdir: ağ isteği, veritabanı çalışması, karmaşık hesaplamalar. iOS'ta eşdeğer, diyalogsuz UI donmasıdır.
SwiftUI otomatik olarak body ve modifier'ın Main Thread'de yürütülmesini garanti eder. Ancak, bir arka plan iş parçacığından (örneğin, bir URLSession temsilcisinden) @Published özelliklerinde veya State'te yapılan değişiklikler sorunlara neden olabilir. ObservableObject sınıfları için @MainActor kullanın, böylece tüm yöntemleri Main Thread'de yürütülür. SwiftUI 5.5+'ta @MainActor, ObservableObject için otomatik olarak eklenir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun