Section 5/172 menit

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:

swift
@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.

swift
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.

swift
// 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:

swift
@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

swift
// 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).