@MainActor — wat is het, toepassing en kenmerken in asynchrone Swift-code

Auteur: IT Sectr Gepubliceerd: 2026-03-19 Leestijd: 8 min

@MainActor is een wereldwijde actor in de programmeertaal Swift die de uitvoering van code op de hoofdthread garandeert. Volgens Apple Developer, 2024 automatiseert @MainActor het schakelen naar de hoofdthread bij het werken met UI, waardoor de ontwikkelaar wordt verlost van het handmatig aanroepen van DispatchQueue.main.async. De annotatie verscheen in Swift 5.5 samen met het async/await-systeem.

Belangrijkste punten

  • @MainActor — wereldwijde Swift-actor voor garantie van uitvoering op de hoofdthread.
  • Het async/await-systeem — de basis waarop @MainActor werkt.
  • Annotatie van de klasse plaatst automatisch al zijn methoden op de hoofdthread.
  • In tegenstelling tot DispatchQueue.main controleert @MainActor de thread op compilerniveau.
  • UI-updates — het belangrijkste toepassingsgebied van @MainActor in iOS-ontwikkeling.

Wat is @MainActor?

@MainActor is een wereldwijde actor (global actor) in Swift die de eigenschappen van actoren combineert met de garantie van uitvoering op de hoofdthread van de applicatie. Het maakt deel uit van het Swift-concurrentiesysteem, geïntroduceerd in Swift 5.5 samen met async/await en gestructureerde concurrentie. De annotatie stelt de ontwikkelaar in staat niet na te denken over het handmatig schakelen van threads en vermindert het aantal UI-fouten.

Definitie en plaats in Swift Concurrency

Een actor in Swift is een referentietype dat zijn toestand isoleert en garandeert dat slechts één thread deze kan wijzigen. @MainActor is een speciale wereldwijde actor waarvan de uitvoerder de hoofdthread is. Alle code gemarkeerd met @MainActor wordt uitgevoerd op de hoofdthread — zelfs als deze is aangeroepen vanuit een achtergrondtaak.

Vóór de komst van @MainActor schakelden ontwikkelaars handmatig naar de hoofdthread via DispatchQueue.main.async. Dit was een bron van veelvuldige fouten: ontwikkelaars vergaten te schakelen, wat leidde tot crashes door UI-updates op een niet-hoofdthread. @MainActor lost dit probleem op op het niveau van het typesysteem.

Redenen voor creatie

De bron van de meeste bugs in iOS-applicaties is UI-onveiligheid — het updaten van de interface vanuit een achtergrondthread. Apple heeft @MainActor ingebouwd in Swift Concurrency om het schakelen naar de hoofdthread automatisch en controleerbaar door de compiler te maken, waarmee een hele klasse runtime-fouten wordt geëlimineerd.

Hoe werkt @MainActor?

Het werkingsprincipe van @MainActor is gebaseerd op het uitvoeringssysteem van Swift Concurrency. Wanneer een thread een functie aanroept die is gemarkeerd met @MainActor, onderbreekt de planner deze op de huidige uitvoerder en hervat deze op de hoofdthread. De compiler volgt de aanroepgrenzen en garandeert veiligheid.

Uitvoerder van de hoofdthread

Voor de uitvoering van @MainActor is MainActor.shared verantwoordelijk — de uitvoerder die is gekoppeld aan de hoofdthread van de applicatie. Wanneer een asynchrone functie is gemarkeerd met @MainActor, wordt deze altijd hervat op deze uitvoerder, ongeacht op welke thread de oorspronkelijke taak is gestart.

swift
import SwiftUI

class ViewModel: ObservableObject {
    @Published var items: [String] = []

    @MainActor
    func loadData() async {
        let result = await fetchRemoteData()
        items = result  // veilig, MainActor garandeert de hoofdthread
    }
}

Overerving van de actorcontext

Als een functie is gemarkeerd met @MainActor en een andere asynchrone functie aanroept, erft deze standaard de actorcontext. Dit betekent dat alle geneste aanroepen ook worden uitgevoerd op de hoofdthread, tenzij anders aangegeven. De compiler volgt dit en geeft een fout bij een poging om een incompatibele closure door te geven.

@MainActor vs DispatchQueue.main

Vergelijking van @MainActor en DispatchQueue.main helpt te begrijpen waarom het nieuwe mechanisme als veiliger en handiger wordt beschouwd, hoewel beide dezelfde taak oplossen — uitvoering van code op de hoofdthread.

Veiligheid op typeniveau

@MainActor is een controle op compilerniveau. Als je probeert een @MainActor-functie aan te roepen vanuit een onveilige context, geeft de compiler een waarschuwing of fout. DispatchQueue.main.async is een runtime-aanroep: de code compileert, maar kan crashen in runtime bij een poging om UI te updaten vanuit een achtergrondthread.

Prestaties en redundantie

DispatchQueue.main.async voegt een blok toe aan de wachtrij dat met vertraging kan worden uitgevoerd. @MainActor met async/await voert een directe schakeling van de uitvoerder uit zonder extra closures te creëren. Dit vermindert overhead en maakt de code voorspelbaarder qua uitvoeringstijd.

swift
// Oude aanpak
DispatchQueue.main.async {
    self.updateUI()
}

// Nieuwe aanpak met @MainActor
@MainActor
func updateUI() {
    // wordt uitgevoerd op de hoofdthread
    self.label.text = "Bijgewerkt"
}
Criterium@MainActorDispatchQueue.main
Controlecompilerruntime
Syntaxisannotatie (declaratief)aanroep (imperatief)
Overheadlaag (uitvoerder schakelen)gemiddeld (closure + wachtrij)
Testbaarheidhoog (MainActor.shared kan worden vervangen)laag (moeilijk te mocken)

Gebruik van @MainActor in iOS-projecten

In echte iOS-projecten wordt @MainActor toegepast in ViewModel-lagen, SwiftUI-weergaven en UIKit-controllers. De annotatie kan zowel op afzonderlijke methoden als op het hele type worden toegepast.

Annotatie van de klasse of structuur

Door een klasse te markeren met @MainActor garandeer je dat al zijn methoden en eigenschappen alleen toegankelijk zijn op de hoofdthread. Dit is vooral handig voor SwiftUI-weergaven en ObservableObject-klassen: je voegt gewoon @MainActor toe vóór class, en alle @Published-eigenschappen worden veilig geüpdatet.

swift
@MainActor
final class UserListViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false

    func fetchUsers() async {
        isLoading = true
        users = await api.getUsers()
        isLoading = false
    }
}

Inpakken van legacy-code

Bij het werken met oude UIKit-code, waar het schakelen van threads handmatig was, kan MainActor.run worden gebruikt voor expliciet schakelen. Dit is handig voor een geleidelijke overgang naar Swift Concurrency zonder de hele codebase te herschrijven.

swift
await MainActor.run {
    self.tableView.reloadData()
}

Beperkingen van @MainActor

Ondanks alle voordelen heeft @MainActor een aantal beperkingen die in overweging moeten worden genomen bij het ontwerpen van de applicatiearchitectuur. Inzicht in de toepassingsgrenzen helpt verkeerd gebruik te voorkomen.

Prestaties bij intensief gebruik

Als de hele aanroepketen is gemarkeerd met @MainActor, wordt elk zwaar werk uitgevoerd op de hoofdthread, wat UI-bevriezing veroorzaakt. Het wordt aanbevolen om alleen de UI-laag te markeren met @MainActor en bedrijfslogica en netwerkverzoeken in achtergrondactoren of de globale uitvoerder te laten.

Incompatibiliteit met sommige API's

Oude callback-gebaseerde API's (bijv. URLSession zonder async/await) ondersteunen de actorcontext niet. Voor integratie is een wrapper met CheckedContinuation vereist. Ook is @MainActor niet compatibel met performSelector, target-action en andere niet-asynchrone UIKit-patronen.

Multi-thread debugging

Bij het debuggen van applicaties met @MainActor is het moeilijker om race-omstandigheden te reproduceren, omdat de compiler er veel van voorkomt in de bouwfase, niet in runtime. Dit kan echter een vals gevoel van veiligheid creëren: onjuist werken met gedeelde mutable objecten (bijv. NSCache of gedeelde globale variabelen) is nog steeds mogelijk als ze niet zijn gemarkeerd met @MainActor en zonder expliciete synchronisatie worden gebruikt.

Testen van @MainActor

@MainActor vereenvoudigt het testen van UI-logica aanzienlijk, omdat het de noodzaak elimineert om handmatig threads te schakelen in tests. Er zijn echter kenmerken waarmee rekening moet worden gehouden bij het schrijven van unittesten en UI-tests.

Unittests met MainActor

In XCTest configureert de uitvoeringsomgeving automatisch de uitvoerder van de hoofdthread. Wanneer de testmethode op de hoofdthread wordt uitgevoerd, vereist het aanroepen van @MainActor-functies geen extra configuratie — ze worden in dezelfde context uitgevoerd. Voor het testen van achtergrondscenario's gebruik je MainActor.run binnen Task met expliciete specificatie van prioriteit en uitvoerder, waarbij je afzonderlijk controleert dat de code correct werkt bij aanroep vanuit de achtergrond.

Een van de gebruikelijke benaderingen is het testen van ViewModel met @MainActor, waarbij wordt gecontroleerd dat @Published-eigenschappen correct worden bijgewerkt na asynchrone bewerkingen. Dankzij overerving van de actorcontext garandeert de await-aanroep binnen de test uitvoering op de hoofdthread zonder extra DispatchQueue-garanties en handmatige contextomschakeling, wat het schrijven van tests vereenvoudigt.

Controle van isolatie bij refactoring

Bij het refactoren van bestaande code naar Swift Concurrency controleer je de @MainActor-isolatie via de compiler: elke aanroep van synchrone methoden zonder @MainActor vanuit de @MainActor-context wordt gemarkeerd als een fout. Deze eigenschap wordt gebruikt voor de geleidelijke overgang van het project naar async/await: je markeert de ViewModel-laag als @MainActor, en de compiler markeert alle onveilige aanroepen die naar achtergrondactoren moeten worden verplaatst.

Mocken en actorcontext

Bij het maken van mocks voor @MainActor-afhankelijkheden gebruik je protocollen met async-methoden die asynchrone functies met retourtypen declareren. Dit maakt het mogelijk om netwerkdiensten, databases en andere externe afhankelijkheden te vervangen zonder de actorisolatie te schenden. De compiler controleert of de mock voldoet aan alle isolatievereisten, waardoor onbedoelde toegang tot @MainActor-code vanuit achtergrondtestthreads wordt voorkomen.

Wachten op asynchrone bewerkingen

Bij het synchroon testen van @MainActor-code gebruik je XCTestExpectation om te wachten op voltooiing van asynchrone bewerkingen. Stel de verwachting in de test in en voer fulfillment uit binnen de closure die op de hoofdthread wordt uitgevoerd. Als de test oneindig blijft hangen — vindt de aanroep op de hoofdthread waarschijnlijk niet plaats en moet de actorisolatie worden gecontroleerd. Voor het debuggen van de uitvoeringscontext is het nuttig om Thread.isMainThread-controle toe te voegen in de testcode.

Veelgestelde vragen

Moet ik de hele klasse markeren met @MainActor?

Nee, het is voldoende om alleen de methoden te markeren die de UI updaten. Als er echter meerdere van dergelijke methoden in de klasse zijn, is het eenvoudiger om @MainActor aan de hele klasse toe te voegen. Dit garandeert dat al zijn leden op de hoofdthread worden uitgevoerd en vereenvoudigt het onderhoud van de code.

Wat is het verschil tussen @MainActor en @globalActor?

@MainActor is een concrete instantie van de wereldwijde actor, gekoppeld aan de hoofdthread. @globalActor is een protocol voor het maken van je eigen wereldwijde actoren. Je kunt bijvoorbeeld @BackgroundActor maken voor het uitvoeren van code op een achtergrondthread, als de projectarchitectuur dit vereist.

Kan @MainActor worden gebruikt zonder async/await?

Ja, synchrone functies met @MainActor worden ook op de hoofdthread uitgevoerd. De belangrijkste waarde van @MainActor komt echter tot uiting met async/await, wanneer de asynchrone functie automatisch wordt hervat op de hoofdthread zonder handmatige schakeling via DispatchQueue.main.

Hoe annuleer ik een @MainActor-taak?

Task.cancel() werkt met @MainActor-taken hetzelfde als met gewone taken. Een @MainActor-taak kan Task.isCancelled controleren of CancellationError gooien. Bij annulering wordt de hoofdthread niet geblokkeerd — de taak stopt eenvoudig met uitvoeren op het dichtstbijzijnde opschortingspunt.

Wat gebeurt er als @MainActor vanuit een achtergrondthread wordt aangeroepen?

De compiler garandeert veiligheid: als je een @MainActor-functie aanroept vanuit een achtergrondcontext, geeft de compiler een fout aan. Voor asynchrone aanroepen is het voldoende om de aanroepende code met await te markeren, en de uitvoerder schakelt zelf naar de hoofdthread. Voor synchrone aanroepen is expliciete schakeling via MainActor.run vereist.

Samenvatting

  • @MainActor — wereldwijde Swift-actor die uitvoering op de hoofdthread garandeert.
  • Compilervalidatie elimineert een hele klasse UI-veiligheidsfouten.
  • Annotatie van de hele klasse plaatst automatisch al zijn methoden op de hoofdthread.
  • MainActor.run — expliciete schakeling voor legacy-code en synchrone contexten.
  • In tegenstelling tot DispatchQueue.main creëert @MainActor geen closures en gebruikt het schakelen van de uitvoerder.
  • Zware berekeningen mogen niet onder @MainActor worden uitgevoerd om UI-bevriezing te voorkomen.
  • Overerving van context van de actor vereenvoudigt asynchrone aanroepketens en maakt code sequentieel, voorspelbaar en veilig voor UI.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook