Ang @MainActor ay isang global na aktor sa wikang Swift na ginagarantiyahan ang pagpapatupad ng code sa pangunahing thread. Ayon sa Apple Developer, 2024, ina-automate ng @MainActor ang paglipat sa pangunahing thread kapag nagtatrabaho sa UI, pinapalaya ang developer mula sa manu-manong pagtawag ng DispatchQueue.main.async. Lumitaw ang anotasyon sa Swift 5.5 kasama ng async/await system.
Mga pangunahing punto
@MainActor ay isang global na aktor (global actor) sa Swift na pinagsasama ang mga katangian ng mga aktor na may garantiya ng pagpapatupad sa pangunahing thread ng aplikasyon. Ito ay bahagi ng sistema ng konkurensiya ng Swift, na ipinakilala sa Swift 5.5 kasama ang async/await at structured na konkurensiya. Ang anotasyon ay nagpapahintulot sa developer na huwag mag-isip tungkol sa manu-manong paglipat ng thread at binabawasan ang bilang ng mga error sa UI.
Ang aktor sa Swift ay isang uri ng referensya na naghihiwalay ng estado nito at ginagarantiyahan na isang thread lamang ang maaaring magbago nito. Ang @MainActor ay isang espesyal na global na aktor na ang tagapagpatupad ay ang pangunahing thread. Anumang code na minarkahan ng @MainActor ay isinasagawa sa pangunahing thread — kahit na tinawag ito mula sa isang background na gawain.
Bago ang pagdating ng @MainActor, ang mga developer ay manu-manong lumilipat sa pangunahing thread sa pamamagitan ng DispatchQueue.main.async. Ito ay pinagmulan ng madalas na mga error: nakalimutan ng mga developer na lumipat, na humantong sa mga crash dahil sa pag-update ng UI sa hindi pangunahing thread. Nilulutas ng @MainActor ang problemang ito sa antas ng sistema ng uri.
Ang pinagmulan ng karamihan sa mga bug sa mga aplikasyon ng iOS ay kawalan ng kaligtasan ng UI — pag-update ng interface mula sa isang background na thread. Isinama ng Apple ang @MainActor sa Swift Concurrency upang gawing awtomatiko at masusuri ng compiler ang paglipat sa pangunahing thread, inaalis ang isang buong klase ng mga error sa runtime.
Prinsipyo ng paggana ng @MainActor ay batay sa sistema ng pagpapatupad ng Swift Concurrency. Kapag ang isang thread ay tumawag ng isang function na minarkahan ng @MainActor, isinasuspinde ito ng tagapag-iskedyul sa kasalukuyang tagapagpatupad at ipinagpatuloy ito sa pangunahing thread. Sinusubaybayan ng compiler ang mga hangganan ng tawag at ginagarantiyahan ang kaligtasan.
Para sa pagpapatupad ng @MainActor ay responsable ang MainActor.shared — ang tagapagpatupad na nauugnay sa pangunahing thread ng aplikasyon. Kapag ang isang asinkronong function ay minarkahan ng @MainActor, ito ay palaging ipinagpatuloy sa tagapagpatupad na ito, hindi alintana kung saang thread sinimulan ang orihinal na gawain.
import SwiftUI
class ViewModel: ObservableObject {
@Published var items: [String] = []
@MainActor
func loadData() async {
let result = await fetchRemoteData()
items = result // ligtas, ginagarantiyahan ng MainActor ang pangunahing thread
}
}
Kung ang isang function ay minarkahan ng @MainActor at tumawag ng isa pang asinkronong function, bilang default ay nagmamana ito ng konteksto ng aktor. Nangangahulugan ito na ang lahat ng nested na tawag ay isinasagawa din sa pangunahing thread, maliban kung iba ang tinukoy. Sinusubaybayan ito ng compiler at nagbibigay ng error kapag sinubukang magpasa ng hindi tugmang closure.
Ang paghahambing ng @MainActor at DispatchQueue.main ay tumutulong na maunawaan kung bakit ang bagong mekanismo ay itinuturing na mas ligtas at maginhawa, kahit na pareho silang lumulutas ng parehong gawain — pagpapatupad ng code sa pangunahing thread.
Ang @MainActor ay isang pagsusuri sa antas ng compiler. Kung susubukan mong tumawag ng @MainActor function mula sa isang hindi ligtas na konteksto, ang compiler ay magbibigay ng babala o error. Ang DispatchQueue.main.async ay isang runtime na tawag: ang code ay magko-compile, ngunit maaaring mag-crash sa runtime kapag sinusubukang i-update ang UI mula sa isang background na thread.
Ang DispatchQueue.main.async ay nagdaragdag ng isang bloke sa pila na maaaring isagawa nang may pagkaantala. Ang @MainActor na may async/await ay nagsasagawa ng direktang paglipat ng tagapagpatupad nang hindi lumilikha ng mga karagdagang closure. Binabawasan nito ang overhead at ginagawang mas predictable ang code sa mga tuntunin ng oras ng pagpapatupad.
// Lumang approach
DispatchQueue.main.async {
self.updateUI()
}
// Bagong approach gamit ang @MainActor
@MainActor
func updateUI() {
// isinasagawa sa pangunahing thread
self.label.text = "Na-update"
}
| Kriterya | @MainActor | DispatchQueue.main |
|---|---|---|
| Pagsusuri | compiler | runtime |
| Sintaks | anotasyon (deklaratibo) | tawag (imperatibo) |
| Overhead | mababa (paglipat ng tagapagpatupad) | katamtaman (closure + pila) |
| Kakayahang masuri | mataas (MainActor.shared ay maaaring palitan) | mababa (mahirap i-mock) |
Sa mga totoong iOS na proyekto, ang @MainActor ay inilalapat sa mga layer ng ViewModel, SwiftUI view at UIKit controller. Ang anotasyon ay maaaring ilapat sa parehong indibidwal na pamamaraan at buong uri.
Sa pamamagitan ng pagmamarka ng isang klase na may @MainActor, ginagarantiyahan mo na ang lahat ng pamamaraan at katangian nito ay maa-access lamang sa pangunahing thread. Ito ay lalong maginhawa para sa SwiftUI view at ObservableObject na mga klase: idaragdag mo lang ang @MainActor bago ang class, at lahat ng @Published na katangian ay ligtas na na-update.
@MainActor
final class UserListViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
func fetchUsers() async {
isLoading = true
users = await api.getUsers()
isLoading = false
}
}
Kapag nagtatrabaho sa lumang UIKit code, kung saan ang paglipat ng thread ay manu-mano, maaaring gamitin ang MainActor.run para sa tahasang paglipat. Ito ay maginhawa para sa unti-unting paglipat sa Swift Concurrency nang hindi muling isinusulat ang buong codebase.
await MainActor.run {
self.tableView.reloadData()
}
Sa kabila ng lahat ng mga pakinabang, ang @MainActor ay may ilang mga limitasyon na mahalagang isaalang-alang kapag nagdidisenyo ng arkitektura ng aplikasyon. Ang pag-unawa sa mga hangganan ng aplikasyon ay tumutulong na maiwasan ang maling paggamit.
Kung ang buong chain ng tawag ay minarkahan ng @MainActor, ang anumang mabigat na trabaho ay isasagawa sa pangunahing thread, na magdudulot ng pag-freeze ng UI. Inirerekomenda na markahan lamang ang UI layer ng @MainActor, at iwanan ang business logic at network request sa background na aktor o global na tagapagpatupad.
Ang mga lumang API na nakabatay sa callback (hal. URLSession nang walang async/await) ay hindi sumusuporta sa konteksto ng aktor. Para sa pagsasama, kinakailangan ang isang wrapper na may CheckedContinuation. Gayundin, ang @MainActor ay hindi tugma sa performSelector, target-action at iba pang hindi asinkronong pattern ng UIKit.
Kapag nagde-debug ng mga aplikasyon na may @MainActor, mas mahirap i-reproduce ang mga kondisyon ng race, dahil pinipigilan ng compiler ang marami sa kanila sa yugto ng pagbuo, hindi sa runtime. Gayunpaman, maaari itong lumikha ng maling pakiramdam ng seguridad: ang hindi tamang pagtatrabaho sa mga shared mutable na bagay (hal. NSCache o shared global variable) ay posible pa rin kung hindi sila minarkahan ng @MainActor at ginamit nang walang tahasang synchronisasyon.
@MainActor ay makabuluhang pinapasimple ang pagsubok ng UI logic, dahil inaalis nito ang pangangailangan para sa manu-manong paglipat ng thread sa mga pagsubok. Gayunpaman, may mga tampok na dapat isaalang-alang kapag nagsusulat ng mga unit test at UI test.
Sa XCTest, ang kapaligiran ng pagpapatupad ay awtomatikong nagko-configure ng tagapagpatupad ng pangunahing thread. Kapag ang test method ay isinasagawa sa pangunahing thread, ang pagtawag ng mga @MainActor function ay hindi nangangailangan ng karagdagang setup — sila ay isinasagawa sa parehong konteksto. Para sa pagsubok ng mga background scenario, gamitin ang MainActor.run sa loob ng Task na may tahasang pagtukoy ng priyoridad at tagapagpatupad, hiwalay na suriin na ang code ay gumagana nang tama kapag tinawag mula sa background.
Ang isa sa mga karaniwang approach ay ang pagsubok ng ViewModel na may @MainActor, kung saan sinusuri na ang @Published na mga katangian ay na-update nang tama pagkatapos ng mga asinkronong operasyon. Dahil sa pagmamana ng konteksto ng aktor, ang tawag na await sa loob ng test ay ginagarantiyahan ang pagpapatupad sa pangunahing thread nang walang karagdagang DispatchQueue na garantiya at manu-manong paglipat ng konteksto, na nagpapasimple sa pagsulat ng mga pagsubok.
Kapag nire-refactor ang umiiral na code sa Swift Concurrency, suriin ang isolasyon ng @MainActor sa pamamagitan ng compiler: anumang tawag ng mga synchronous na pamamaraan nang walang @MainActor mula sa @MainActor na konteksto ay minarkahan bilang error. Ang katangiang ito ay ginagamit para sa unti-unting paglipat ng proyekto sa async/await: minarkahan mo ang ViewModel layer bilang @MainActor, at itinatampok ng compiler ang lahat ng hindi ligtas na tawag na kailangang ilipat sa background na mga aktor.
Kapag lumilikha ng mga mock para sa @MainActor dependencies, gumamit ng mga protocol na may async na pamamaraan na nagdedeklara ng mga asinkronong function na may mga return type. Pinapayagan nito ang pagpapalit ng mga network service, database at iba pang panlabas na dependencies nang hindi lumalabag sa aktor na isolasyon. Susuriin ng compiler na ang mock ay nakakatugon sa lahat ng mga kinakailangan sa isolasyon, na pumipigil sa hindi sinasadyang pag-access sa @MainActor code mula sa background test threads.
Kapag sinusubok nang synchronous ang @MainActor code, gamitin ang XCTestExpectation para maghintay ng pagkumpleto ng mga asinkronong operasyon. Itakda ang inaasahan sa test at gawin ang fulfillment sa loob ng closure na isinasagawa sa pangunahing thread. Kung ang test ay nakabitin nang walang katapusan — malamang na ang tawag sa pangunahing thread ay hindi nangyayari at kailangang suriin ang aktor na isolasyon. Para sa debugging ng konteksto ng pagpapatupad, kapaki-pakinabang na magdagdag ng pagsusuri ng Thread.isMainThread sa loob ng test code.
Mga madalas itanong
Hindi, sapat na markahan lamang ang mga pamamaraan na nag-a-update ng UI. Gayunpaman, kung maraming ganoong pamamaraan sa klase, mas madaling idagdag ang @MainActor sa buong klase. Ginagarantiyahan nito na ang lahat ng miyembro nito ay isinasagawa sa pangunahing thread at pinapasimple ang pagpapanatili ng code.
@MainActor ay isang kongkretong instance ng global na aktor, na nakatali sa pangunahing thread. Ang @globalActor ay isang protocol para sa paglikha ng iyong sariling global na mga aktor. Halimbawa, maaari kang lumikha ng @BackgroundActor para sa pagpapatupad ng code sa isang background na thread, kung kinakailangan ito ng arkitektura ng proyekto.
Oo, ang mga synchronous function na may @MainActor ay isinasagawa din sa pangunahing thread. Gayunpaman, ang pangunahing halaga ng @MainActor ay nahahayag sa async/await, kapag ang asinkronong function ay awtomatikong ipinagpatuloy sa pangunahing thread nang walang manu-manong paglipat sa pamamagitan ng DispatchQueue.main.
Task.cancel() ay gumagana sa @MainActor na mga gawain tulad ng sa mga ordinaryong gawain. Ang @MainActor na gawain ay maaaring suriin ang Task.isCancelled o magtapon ng CancellationError. Sa pagkansela, ang pangunahing thread ay hindi nahaharang — ang gawain ay humihinto lamang sa pagpapatupad sa pinakamalapit na suspension point.
Ang compiler ay ginagarantiyahan ang kaligtasan: kung tatawag ka ng @MainActor function mula sa isang background na konteksto, ituturo ng compiler ang error. Para sa mga asinkronong tawag, sapat na markahan ang tumatawag na code ng await, at ang tagapagpatupad ay lilipat mismo sa pangunahing thread. Para sa mga synchronous na tawag, kinakailangan ang tahasang paglipat sa pamamagitan ng MainActor.run.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din