Actor — is een type geïntroduceerd in Swift 5.5+ dat het probleem van race conditions op taalniveau oplost. In tegenstelling tot handmatige vergrendelingen en DispatchQueue-wachtrijen, isoleert een actor automatisch zijn status en synchroniseert de toegang ernaartoe. Dit betekent dat twee threads niet tegelijkertijd dezelfde eigenschap van een actortype kunnen wijzigen, wat race conditions uitsluit zonder extra inspanning van de ontwikkelaar. Het mechanisme is gebaseerd op het concept van actor-isolatie, waarbij de compiler toegang tot eigenschappen en methoden van de actor controleert. Volgens Apple, 2025 is het actormodel een fundamenteel onderdeel van het concurrency-systeem van Swift.
Belangrijkste punten
Actor — is een referentietype, vergelijkbaar met een klasse, maar met automatische bescherming tegen race conditions. Het werd geïntroduceerd in Swift 5.5 als onderdeel van het concurrency-systeem naast async/await en Task. Een actor garandeert dat zijn veranderlijke status nooit gelijktijdig wordt gelezen of geschreven vanuit verschillende threads zonder expliciete synchronisatie.
Om een actor te declareren wordt het sleutelwoord actor gebruikt, gevolgd door accolades met zijn leden. Qua syntaxis lijkt een actor op een klasse, maar het gedrag is radicaal anders.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
self.balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
De compiler isoleert automatisch alle eigenschappen en methoden van de actor, zodat ze alleen binnen de actorcontext toegankelijk zijn. Een poging om balance van buiten de actor te benaderen, resulteert in een compilatiefout als de aanroep niet is gemarkeerd als async.
Gegevensisolatie — is het belangrijkste concept van de actor. De actor garandeert wederzijds exclusieve toegang tot zijn status via het mechanisme van de actoruitvoerder (actor executor). Elke actor heeft zijn eigen uitvoerder die alle toegangen tot zijn geïsoleerde leden sequentieel verwerkt.
Wanneer code buiten de actor zijn methode aanroept, wordt de aanroep in de wachtrij van de actoruitvoerder geplaatst. De actor voert slechts één taak tegelijk uit, wat de afwezigheid van race conditions garandeert. Als twee threads tegelijkertijd deposit aanroepen, wacht de tweede aanroep op voltooiing van de eerste.
let account = BankAccount(initialBalance: 1000.0)
// Asynchrone aanroep — vereist van buiten de actor
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()
Elke aanroep van een actormethode vereist await, omdat de actor mogelijk bezig is met een andere taak. Dit is geen bug, maar een bewust ontwerp om race conditions te voorkomen. Swift maakt getters en setters van actoreigenschappen impliciet async, dus het lezen van een eigenschap vereist ook await.
Actor en klasse zijn referentietypen, maar hun gedrag in een multi-threaded omgeving verschilt radicaal. Een klasse biedt geen enkele automatische bescherming tegen race conditions, terwijl een actor deze op compilerniveau inbouwt via het typesysteem.
| Kenmerk | Actor | Class |
|---|---|---|
| Bescherming tegen races | Automatisch, op compilerniveau | Vereist handmatige synchronisatie |
| Toegang tot eigenschappen | Alleen via await van buitenaf | Direct, zonder synchronisatie |
| Overerving | Alleen van andere actors | Standaard klasse-overerving |
| Protocolconformiteit | Kan voldoen aan protocollen | Standaard |
| Prestaties | Lage overhead bij isolatie | Zonder synchronisatie — sneller |
Een actor kan alleen overerven van een andere actor en niet van een klasse. Dit is bewust gedaan, omdat een klasse geen mechanisme voor actorisolatie heeft en het mengen van twee modellen zou leiden tot ongedefinieerd gedrag.
actor SavingsAccount: BankAccount {
func applyInterest(rate: Double) {
let interest = balance * rate
balance += interest
}
}
Asynchrone aanroepen — zijn het mechanisme voor interactie met de actor vanuit externe code. Omdat de actor zijn status isoleert, vereist elke toegang tot zijn leden van buitenaf await. Dit stelt Swift in staat te garanderen dat de aanroepende code de thread niet blokkeert en de actor andere verzoeken kan verwerken.
Naast gedeclareerde actortypen ondersteunt Swift globale actors — het @MainActor-attribuut, dat klassen, eigenschappen of methoden markeert als uitgevoerd op de hoofdthread. Dit is vooral handig bij het werken met UIKit en SwiftUI.
@MainActor
class ViewModel: ObservableObject {
@Published var title: String = ""
func updateTitle() {
// Deze code wordt gegarandeerd op de hoofdthread uitgevoerd
title = "New Title"
}
}
Het gebruik van @MainActor elimineert de noodzaak om handmatig DispatchQueue.main.async aan te roepen, wat de code schoner en veiliger maakt. De compiler controleert of de overschakeling naar de hoofdthread correct plaatsvindt.
Nonisolated — is een sleutelwoord waarmee een methode of berekende eigenschap van een actor als niet-geïsoleerd kan worden gemarkeerd. Dergelijke leden hebben geen toegang tot de geïsoleerde status van de actor, maar kunnen van buiten de actor worden aangeroepen zonder await.
Nonisolated methoden zijn nuttig voor berekeningen die niet afhankelijk zijn van de veranderlijke status van de actor. De methode formatBalance heeft bijvoorbeeld geen directe toegang tot balance, maar formatteert alleen de doorgegeven waarde — zo'n methode kan veilig nonisolated worden gemaakt.
actor BankAccount {
private var balance: Double = 0
nonisolated func formatBalance(amount: Double) -> String {
return "$\(amount)"
}
}
Nonisolated leden worden synchroon uitgevoerd en vereisen geen await. Ze kunnen echter niet direct de geïsoleerde eigenschappen van de actor lezen. Als een nonisolated methode een waarde uit de actor nodig heeft, moet deze als parameter worden doorgegeven.
Reentrancy — is een mechanisme dat herhaalde toegang tot de actor mogelijk maakt tijdens het wachten op een asynchrone aanroep. Zonder reentrancy zou de actor voor altijd kunnen blokkeren als een van zijn methoden op een andere wacht, die op zijn beurt op de eerste wacht.
Wanneer code binnen de actor await uitvoert, onderbreekt de actor de huidige taak en kan hij een andere verwerken die in de wachtrij staat. Na voltooiing van await wordt de taak hervat. Dit voorkomt deadlocks, maar vereist voorzichtigheid: de status van de actor tussen await-punten kan veranderen.
actor DataProcessor {
var cache: [Int: String] = [:]
func process(id: Int) async -> String {
if let cached = cache[id] {
return cached
}
// await — herintredingspunt
let result = await fetchData(id: id)
// Cache kan zijn veranderd na await — controleer opnieuw
cache[id] = result
return result
}
}
Ontwikkelaars moeten rekening houden met reentrancy en de status van de actor controleren na await-punten. Een veelgemaakte fout is de veronderstelling dat actorisolatie behouden blijft tijdens asynchrone pauzes. In de praktijk kan de status tussen await en de volgende instructie afwijken van de verwachte.
Veelgestelde vragen
Actor isoleert automatisch zijn status van race conditions en vereist await voor toegang van buitenaf. Een klasse biedt geen dergelijke bescherming — de ontwikkelaar is zelf verantwoordelijk voor synchronisatie via vergrendelingen of wachtrijen. Een actor erft alleen over van een actor, een klasse van een klasse.
Ja, een actor kan overerven van een andere actor. De subklasse krijgt alle geïsoleerde eigenschappen en methoden van de ouder. Een actor kan niet overerven van een klasse, omdat klassen geen mechanisme voor actorisolatie op compilerniveau hebben.
Actor-isolated context — is een codegebied waarin directe toegang tot de veranderlijke status van de actor is toegestaan. Binnen methoden van de actor die als isolated (standaard) zijn gemarkeerd, kunnen eigenschappen worden gelezen en geschreven zonder await. De compiler controleert de isolatiegrenzen.
Gegevens van de actor worden doorgegeven via async-methoden die Sendable-typen retourneren, of via nonisolated methoden die waarden als parameters accepteren. Men kan ook een async-eigenschap maken die een momentopname van de actorstatus retourneert in de vorm van een Sendable-structuur.
Ja, een actor kan voldoen aan protocollen. Als het protocol geïsoleerde vereisten (actor-isolated) bevat, worden deze automatisch actor-geïsoleerd. Voor asynchrone methoden in protocollen kan met de isolated-marker worden aangegeven dat ze op een specifieke actor moeten worden aangeroepen.
Samenvatting
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.
Lees ook