Actor — är en typ som introducerades i Swift 5.5+ som löser problemet med datarace på språknivå. Till skillnad från manuella lås och DispatchQueue-köer isolerar en actor automatiskt sitt tillstånd och synkroniserar åtkomst till det. Detta innebär att två trådar inte samtidigt kan ändra samma egenskap hos en actortyp, vilket eliminerar race condition utan extra ansträngning från utvecklaren. Mekanismen bygger på konceptet actorisolering, där kompilatorn spårar åtkomst till actorns egenskaper och metoder. Enligt Apple, 2025 är actormodellen en grundläggande del av Swifts konkurrenssystem.
Huvudpunkter
Actor — är en referenstyp, liknande en klass, men med automatiskt skydd mot race conditions. Den introducerades i Swift 5.5 som en del av konkurrenssystemet tillsammans med async/await och Task. En actor garanterar att dess föränderliga tillstånd aldrig läses eller skrivs samtidigt från olika trådar utan explicit synkronisering.
För att deklarera en actor används nyckelordet actor, följt av måsvingar med dess medlemmar. Syntaxmässigt liknar en actor en klass, men beteendet är radikalt annorlunda.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
self.balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
Kompilatorn isolerar automatiskt alla egenskaper och metoder för actor så att de endast är tillgängliga inom actorkontexten. Ett försök att komma åt balance utanför actor kommer att orsaka ett kompileringsfel om anropet inte är markerat som async.
Dataseparation — är det viktigaste konceptet för actor. En actor garanterar ömsesidigt uteslutande åtkomst till sitt tillstånd genom mekanismen med actorutförare (actor executor). Varje actor har sin egen utförare som bearbetar all åtkomst till dess isolerade medlemmar sekventiellt.
När kod utanför actor anropar dess metod placeras anropet i kön hos actorutföraren. Actor utför endast en uppgift samtidigt, vilket garanterar frånvaro av race conditions. Om två trådar samtidigt anropar deposit väntar det andra anropet på att det första slutförs.
let account = BankAccount(initialBalance: 1000.0)
// Asynkront anrop — krävs utifrån actor
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()
Varje anrop av en actormetod kräver await, eftersom actor kan vara upptagen med en annan uppgift. Detta är inte en bugg utan en medveten design för att förhindra race conditions. Swift gör implicit getters och setters för actoregenskaper till async, så att läsa en egenskap också kräver await.
Actor och klass är referenstyper, men deras beteende i en flertrådad miljö skiljer sig radikalt. En klass ger inget automatiskt skydd mot race conditions, medan en actor bygger in det på kompilatornivå genom typsystemet.
| Egenskap | Actor | Class |
|---|---|---|
| Skydd mot race | Automatiskt, på kompilatornivå | Kräver manuell synkronisering |
| Åtkomst till egenskaper | Endast via await utifrån | Direkt, utan synkronisering |
| Arv | Endast från andra actors | Standardklassarv |
| Protokollefterlevnad | Kan följa protokoll | Standard |
| Prestanda | Låg overhead vid isolering | Utan synkronisering — snabbare |
En actor kan endast ärva från en annan actor och inte från en klass. Detta är gjort medvetet eftersom en klass inte har någon mekanism för actorisolering, och blandning av två modeller skulle leda till odefinierat beteende.
actor SavingsAccount: BankAccount {
func applyInterest(rate: Double) {
let interest = balance * rate
balance += interest
}
}
Asynkrona anrop — är mekanismen för interaktion med actor från extern kod. Eftersom actor isolerar sitt tillstånd kräver varje åtkomst till dess medlemmar utifrån await. Detta gör att Swift kan garantera att den anropande koden inte blockerar tråden och att actor kan bearbeta andra förfrågningar.
Förutom deklarerade actortyper stödjer Swift globala actors — attributet @MainActor, som markerar klasser, egenskaper eller metoder som körda på huvudtråden. Detta är särskilt användbart när man arbetar med UIKit och SwiftUI.
@MainActor
class ViewModel: ObservableObject {
@Published var title: String = ""
func updateTitle() {
// Denna kod körs garanterat på huvudtråden
title = "New Title"
}
}
Användning av @MainActor eliminerar behovet av att manuellt anropa DispatchQueue.main.async, vilket gör koden renare och säkrare. Kompilatorn kontrollerar att växlingen till huvudtråden sker korrekt.
Nonisolated — är ett nyckelord som gör det möjligt att markera en metod eller beräknad egenskap hos en actor som oisolerad. Sådana medlemmar har inte åtkomst till actorns isolerade tillstånd, men kan anropas utan await utifrån actor.
Nonisolated metoder är användbara för beräkningar som inte är beroende av actorns föränderliga tillstånd. Till exempel metoden formatBalance har inte direkt åtkomst till balance, utan formaterar bara det överförda värdet — en sådan metod kan säkert göras nonisolated.
actor BankAccount {
private var balance: Double = 0
nonisolated func formatBalance(amount: Double) -> String {
return "$\(amount)"
}
}
Nonisolated medlemmar körs synkront och kräver inte await. De kan dock inte direkt läsa actorns isolerade egenskaper. Om en nonisolated metod behöver ett värde från actor måste det överföras som parameter.
Reentrancy — är en mekanism som tillåter återinträde i actor under väntan på ett asynkront anrop. Utan reentrancy skulle actor kunna blockeras för evigt om en av dess metoder väntar på en annan som i sin tur väntar på den första.
När kod inuti actor utför await pausar actor den aktuella uppgiften och kan bearbeta en annan som står i kön. När await är klart återupptas uppgiften. Detta förhindrar deadlocks, men kräver försiktighet: actorns tillstånd mellan await-punkter kan förändras.
actor DataProcessor {
var cache: [Int: String] = [:]
func process(id: Int) async -> String {
if let cached = cache[id] {
return cached
}
// await — återinträdespunkt
let result = await fetchData(id: id)
// Cachen kan ha ändrats efter await — kontrollera igen
cache[id] = result
return result
}
}
Utvecklare måste ta hänsyn till reentrancy och kontrollera actorns tillstånd efter await-punkter. Ett typiskt misstag är antagandet att actorisolering bibehålls genom asynkrona pauser. I praktiken kan tillståndet mellan await och nästa sats skilja sig från det förväntade.
Vanliga frågor
Actor isolerar automatiskt sitt tillstånd från race conditions och kräver await för åtkomst utifrån. En klass ger inte sådant skydd — utvecklaren är själv ansvarig för synkronisering genom lås eller köer. En actor ärver endast från actor, en klass från klass.
Ja, en actor kan ärva från en annan actor. Subklassen får alla isolerade egenskaper och metoder från föräldern. En actor kan inte ärva från en klass eftersom klasser inte har någon mekanism för actorisolering på kompilatornivå.
Actor-isolated kontext — är ett kodområde där direkt åtkomst till actorns föränderliga tillstånd är tillåten. Inuti metoder av actor som är markerade som isolated (standard) kan egenskaper läsas och skrivas utan await. Kompilatorn kontrollerar isoleringsgränserna.
Data från actor överförs via async-metoder som returnerar Sendable-typer, eller via nonisolated metoder som tar emot värden som parametrar. Man kan också skapa en async-egenskap som returnerar en ögonblicksbild av actorns tillstånd i form av en Sendable-struktur.
Ja, en actor kan följa protokoll. Om protokollet innehåller isolerade krav (actor-isolated) blir de automatiskt actor-isolerade. För asynkrona metoder i protokoll kan man med isolated-markeringen ange att de ska anropas på en specifik actor.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också