5. MainActor: Lebih dari Sekadar "Main Thread"
5. MainActor: Lebih dari Sekadar "Main Thread"
@MainActor adalah global actor spesial yang executor-nya adalah main dispatch queue. Tapi properti uniknya jauh lebih dalam dari sekadar "jalan di main thread".
Apa yang Membuat MainActor Spesial
| Properti | MainActor | Actor biasa |
|---|---|---|
| Executor | Selalu main thread | Default executor (work-stealing pool) |
| Thread affinity | Pinned ke main thread | Bisa hop antar thread |
| Tipe | GlobalActor singleton |
Instance per object |
| Akses dari konteks main | Synchronous | Tetap perlu await |
| Re-entrancy | Ya | Ya |
Synchronous Access dari Konteks MainActor
Kalau caller sudah @MainActor-isolated, akses ke property/method @MainActor lain tidak perlu await:
@MainActor
final class ViewModel {
var title: String = ""
func update(_ newTitle: String) {
title = newTitle // sync — sudah di MainActor
}
}
@MainActor
final class Controller {
let vm = ViewModel()
func didTapButton() {
vm.update("hello") // sync — Controller juga @MainActor
}
}
Ini kunci performa SwiftUI / UIKit: state update tidak butuh hop.
MainActor pada Closure: MainActor.run dan MainActor.assumeIsolated
Dua API yang sering dicampur-aduk:
MainActor.run
Suspend → hop ke main → jalankan closure → balik. Selalu hop, bahkan kalau sudah di main.
Task.detached {
let x = await MainActor.run {
self.label.text = "done"
return self.label.text
}
}
MainActor.assumeIsolated
Asersi: "saya sudah di main thread, jalankan synchronously". Crash kalau ternyata bukan. Tidak ada hop.
// Callback dari API yang menjamin main thread tapi tidak isolated
func legacyCallback() {
MainActor.assumeIsolated {
viewModel.title = "from callback" // tidak ada await, tidak ada hop
}
}
Gunakan assumeIsolated saat berurusan dengan API legacy (UIKit delegates lama, completion handler) yang menjamin main thread tapi belum di-annotate @MainActor. Ini menghemat satu hop per call — bisa signifikan di handler yang dipanggil ratusan kali per detik.
MainActor dan Class Inheritance
Sebelum Swift 6, mewarisi class non-isolated dari class @MainActor (atau sebaliknya) memunculkan diagnostic yang membingungkan. Swift 6 lebih ketat:
@MainActor
class BaseViewModel { }
// ✓ OK: subclass MainActor menyatakan ulang
@MainActor
class UserViewModel: BaseViewModel { }
// ❌ Error: subclass tidak bisa menghapus isolation parent
class FreeViewModel: BaseViewModel { }
MainActor di Protocol: @MainActor vs Per-Member
// Seluruh protocol: setiap requirement isolated ke main
@MainActor
protocol ViewModel {
var state: State { get }
func reload() async
}
// Hanya satu requirement isolated
protocol Refreshable {
@MainActor func refresh()
func backgroundWork() async // tidak isolated
}
Aturan: lebih spesifik > umum. Per-member memberi fleksibilitas; whole-protocol mempermudah caller (semuanya sudah pasti di main).