@MainActor adalah aktor global dalam bahasa Swift yang menjamin eksekusi kode pada thread utama. Menurut Apple Developer, 2024, @MainActor mengotomatiskan perpindahan ke thread utama saat bekerja dengan UI, membebaskan pengembang dari panggilan manual DispatchQueue.main.async. Anotasi muncul di Swift 5.5 bersama dengan sistem async/await.
Poin utama
@MainActor adalah aktor global (global actor) di Swift yang menggabungkan properti aktor dengan jaminan eksekusi pada thread utama aplikasi. Ini adalah bagian dari sistem konkurensi Swift, yang diperkenalkan di Swift 5.5 bersama async/await dan konkurensi terstruktur. Anotasi memungkinkan pengembang untuk tidak memikirkan perpindahan thread manual dan mengurangi jumlah kesalahan UI.
Aktor di Swift adalah tipe referensi yang mengisolasi statusnya dan menjamin bahwa hanya satu thread yang dapat mengubahnya. @MainActor adalah aktor global khusus yang eksekutornya adalah thread utama. Kode apa pun yang ditandai dengan @MainActor dieksekusi pada thread utama — bahkan jika dipanggil dari tugas latar belakang.
Sebelum munculnya @MainActor, pengembang beralih secara manual ke thread utama melalui DispatchQueue.main.async. Ini adalah sumber kesalahan yang sering terjadi: pengembang lupa beralih, yang menyebabkan crash karena pembaruan UI di thread non-utama. @MainActor memecahkan masalah ini di level sistem tipe.
Sumber sebagian besar bug di aplikasi iOS adalah ketidakamanan UI — memperbarui antarmuka dari thread latar belakang. Apple menyematkan @MainActor ke dalam Swift Concurrency untuk membuat perpindahan ke thread utama otomatis dan dapat diperiksa oleh compiler, menghilangkan seluruh kelas kesalahan runtime.
Prinsip kerja @MainActor didasarkan pada sistem eksekusi Swift Concurrency. Ketika sebuah thread memanggil fungsi yang ditandai dengan @MainActor, penjadwal menangguhkannya pada eksekutor saat ini dan melanjutkannya pada thread utama. Compiler melacak batas panggilan dan menjamin keamanan.
Untuk eksekusi @MainActor bertanggung jawab MainActor.shared — eksekutor yang terkait dengan thread utama aplikasi. Ketika fungsi asinkron ditandai dengan @MainActor, fungsi tersebut selalu dilanjutkan pada eksekutor ini, terlepas dari thread mana tugas asli dimulai.
import SwiftUI
class ViewModel: ObservableObject {
@Published var items: [String] = []
@MainActor
func loadData() async {
let result = await fetchRemoteData()
items = result // aman, MainActor menjamin thread utama
}
}
Jika sebuah fungsi ditandai dengan @MainActor dan memanggil fungsi asinkron lain, secara default ia mewarisi konteks aktor. Ini berarti semua panggilan bersarang juga dieksekusi pada thread utama, kecuali ditentukan lain. Compiler melacak ini dan memberikan kesalahan saat mencoba mengirimkan closure yang tidak sesuai.
Perbandingan @MainActor dan DispatchQueue.main membantu memahami mengapa mekanisme baru dianggap lebih aman dan nyaman, meskipun keduanya menyelesaikan tugas yang sama — eksekusi kode pada thread utama.
@MainActor adalah pemeriksaan di level compiler. Jika Anda mencoba memanggil fungsi @MainActor dari konteks yang tidak aman, compiler akan memberikan peringatan atau kesalahan. DispatchQueue.main.async adalah panggilan runtime: kode akan dikompilasi, tetapi dapat crash di runtime saat mencoba memperbarui UI dari thread latar belakang.
DispatchQueue.main.async menambahkan blok ke antrian yang dapat dieksekusi dengan penundaan. @MainActor dengan async/await melakukan perpindahan langsung eksekutor tanpa membuat closure tambahan. Ini mengurangi overhead dan membuat kode lebih dapat diprediksi dalam hal waktu eksekusi.
// Pendekatan lama
DispatchQueue.main.async {
self.updateUI()
}
// Pendekatan baru dengan @MainActor
@MainActor
func updateUI() {
// dieksekusi pada thread utama
self.label.text = "Diperbarui"
}
| Kriteria | @MainActor | DispatchQueue.main |
|---|---|---|
| Pemeriksaan | compiler | runtime |
| Sintaks | anotasi (deklaratif) | panggilan (imperatif) |
| Overhead | rendah (perpindahan eksekutor) | sedang (closure + antrian) |
| Kemampuan uji | tinggi (MainActor.shared bisa diganti) | rendah (sulit di-mock) |
Dalam proyek iOS nyata, @MainActor diterapkan di lapisan ViewModel, tampilan SwiftUI dan controller UIKit. Anotasi dapat diterapkan baik pada metode individu maupun seluruh tipe.
Dengan menandai kelas dengan @MainActor, Anda menjamin bahwa semua metode dan propertinya hanya dapat diakses pada thread utama. Ini sangat nyaman untuk tampilan SwiftUI dan kelas ObservableObject: Anda cukup menambahkan @MainActor sebelum class, dan semua properti @Published diperbarui dengan aman.
@MainActor
final class UserListViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
func fetchUsers() async {
isLoading = true
users = await api.getUsers()
isLoading = false
}
}
Saat bekerja dengan kode UIKit lama, di mana perpindahan thread dilakukan secara manual, Anda dapat menggunakan MainActor.run untuk perpindahan eksplisit. Ini nyaman untuk transisi bertahap ke Swift Concurrency tanpa menulis ulang seluruh basis kode.
await MainActor.run {
self.tableView.reloadData()
}
Terlepas dari semua kelebihannya, @MainActor memiliki sejumlah keterbatasan yang harus dipertimbangkan saat merancang arsitektur aplikasi. Memahami batas penerapan membantu menghindari penggunaan yang salah.
Jika seluruh rantai panggilan ditandai dengan @MainActor, setiap pekerjaan berat akan dieksekusi pada thread utama, menyebabkan pembekuan UI. Disarankan untuk menandai hanya lapisan UI dengan @MainActor, dan membiarkan logika bisnis serta permintaan jaringan di aktor latar belakang atau eksekutor global.
API lama berbasis callback (misalnya URLSession tanpa async/await) tidak mendukung konteks aktor. Untuk integrasi, diperlukan pembungkus dengan CheckedContinuation. Juga, @MainActor tidak kompatibel dengan performSelector, target-action, dan pola UIKit non-asinkron lainnya.
Saat melakukan debugging aplikasi dengan @MainActor, lebih sulit untuk mereproduksi kondisi balapan, karena compiler mencegah banyak dari mereka pada fase pembangunan, bukan di runtime. Namun, ini dapat menciptakan rasa aman yang palsu: bekerja secara tidak benar dengan objek mutable bersama (misalnya NSCache atau variabel global bersama) masih mungkin terjadi jika tidak ditandai dengan @MainActor dan digunakan tanpa sinkronisasi eksplisit.
@MainActor secara signifikan menyederhanakan pengujian logika UI, karena menghilangkan kebutuhan untuk beralih thread secara manual dalam pengujian. Namun, ada fitur yang perlu dipertimbangkan saat menulis pengujian unit dan pengujian UI.
Di XCTest, lingkungan eksekusi secara otomatis mengkonfigurasi eksekutor thread utama. Ketika metode pengujian dijalankan pada thread utama, pemanggilan fungsi @MainActor tidak memerlukan pengaturan tambahan — mereka dieksekusi dalam konteks yang sama. Untuk menguji skenario latar belakang, gunakan MainActor.run di dalam Task dengan menentukan prioritas dan eksekutor secara eksplisit, periksa secara terpisah bahwa kode berfungsi dengan benar saat dipanggil dari latar belakang.
Salah satu pendekatan umum adalah menguji ViewModel dengan @MainActor, di mana diperiksa bahwa properti @Published diperbarui dengan benar setelah operasi asinkron. Berkat warisan konteks aktor, panggilan await di dalam pengujian menjamin eksekusi pada thread utama tanpa jaminan DispatchQueue tambahan dan perpindahan konteks manual, yang menyederhanakan penulisan pengujian.
Saat merefaktor kode yang ada ke Swift Concurrency, periksa isolasi @MainActor melalui compiler: setiap panggilan metode sinkron tanpa @MainActor dari konteks @MainActor ditandai sebagai kesalahan. Properti ini digunakan untuk transisi bertahap proyek ke async/await: Anda menandai lapisan ViewModel sebagai @MainActor, dan compiler menyoroti semua panggilan tidak aman yang perlu dipindahkan ke aktor latar belakang.
Saat membuat mock untuk dependensi @MainActor, gunakan protokol dengan metode async yang mendeklarasikan fungsi asinkron dengan tipe pengembalian. Ini memungkinkan penggantian layanan jaringan, database, dan dependensi eksternal lainnya tanpa melanggar isolasi aktor. Compiler akan memeriksa bahwa mock memenuhi semua persyaratan isolasi, mencegah akses tidak sengaja ke kode @MainActor dari thread pengujian latar belakang.
Saat menguji kode @MainActor secara sinkron, gunakan XCTestExpectation untuk menunggu penyelesaian operasi asinkron. Atur ekspektasi dalam pengujian dan lakukan fulfillment di dalam closure yang dieksekusi pada thread utama. Jika pengujian menggantung tanpa batas — kemungkinan besar panggilan pada thread utama tidak terjadi dan isolasi aktor perlu diperiksa. Untuk debugging konteks eksekusi, berguna untuk menambahkan pemeriksaan Thread.isMainThread di dalam kode pengujian.
Pertanyaan yang sering diajukan
Tidak, cukup menandai hanya metode yang memperbarui UI. Namun, jika ada beberapa metode seperti itu di kelas, lebih mudah menambahkan @MainActor ke seluruh kelas. Ini menjamin bahwa semua anggotanya dieksekusi pada thread utama dan menyederhanakan pemeliharaan kode.
@MainActor adalah instance konkret dari aktor global, terikat ke thread utama. @globalActor adalah protokol untuk membuat aktor global Anda sendiri. Misalnya, Anda dapat membuat @BackgroundActor untuk mengeksekusi kode pada thread latar belakang, jika arsitektur proyek membutuhkannya.
Ya, fungsi sinkron dengan @MainActor juga dieksekusi pada thread utama. Namun, nilai utama @MainActor terungkap tepat dengan async/await, ketika fungsi asinkron secara otomatis dilanjutkan pada thread utama tanpa perpindahan manual melalui DispatchQueue.main.
Task.cancel() bekerja dengan tugas @MainActor sama seperti tugas biasa. Tugas @MainActor dapat memeriksa Task.isCancelled atau melempar CancellationError. Saat pembatalan, thread utama tidak diblokir — tugas hanya berhenti dieksekusi pada titik penangguhan terdekat.
Compiler menjamin keamanan: jika Anda memanggil fungsi @MainActor dari konteks latar belakang, compiler akan menunjukkan kesalahan. Untuk panggilan asinkron, cukup menandai kode pemanggil dengan await, dan eksekutor akan beralih sendiri ke thread utama. Untuk panggilan sinkron, diperlukan perpindahan eksplisit melalui MainActor.run.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga