Section 6/171 menit

6. Best Practice: Penggunaan MainActor

6. Best Practice: Penggunaan MainActor

BP 6.1: Annotate di Level Type, Bukan Method per Method

Kalau seluruh class adalah UI, tandai @MainActor di class. Lebih bersih, dan caller tidak perlu cek per method.

swift
// ❌ Per method
final class HomeViewModel {
    @MainActor var items: [Item] = []
    @MainActor func reload() async { ... }
    @MainActor func toggle(_ id: UUID) { ... }
}

// ✓ Per class
@MainActor
final class HomeViewModel {
    var items: [Item] = []
    func reload() async { ... }
    func toggle(_ id: UUID) { ... }
}

BP 6.2: Jangan Letakkan CPU-Heavy di MainActor

Parsing, encryption, image processing, sorting list besar — bukan di MainActor. Pindahkan ke actor lain atau function nonisolated.

swift
// ❌ Block main thread
@MainActor
final class SearchViewModel {
    func search(_ q: String) {
        let results = heavyFuzzyMatch(q, in: bigCorpus)  // CPU-heavy di main
        self.results = results
    }
}

// ✓ Off-main untuk CPU
@MainActor
final class SearchViewModel {
    var results: [Item] = []

    func search(_ q: String) async {
        let found = await Task.detached(priority: .userInitiated) {
            heavyFuzzyMatch(q, in: bigCorpus)
        }.value
        self.results = found  // kembali ke main
    }
}

BP 6.3: Pakai assumeIsolated di Callback Lama yang Sudah di Main

Banyak API UIKit/AppKit memanggil callback di main thread tapi belum di-annotate @MainActor. Pakai MainActor.assumeIsolated untuk menghindari hop yang tidak perlu.

swift
// ✓ Tanpa hop
button.addTarget(self, action: #selector(handleTap), for: .touchUpInside)

@objc private func handleTap() {
    MainActor.assumeIsolated {
        viewModel.didTapButton()  // tidak ada await, tidak ada hop
    }
}

BP 6.4: Update UI dalam Batch

Setiap await MainActor.run { } adalah hop. Hop berlebihan = lag visual.

swift
// ❌ Per item hop
for change in changes {
    await MainActor.run { table.apply(change) }
}

// ✓ Satu hop
await MainActor.run {
    for change in changes { table.apply(change) }
}