Actor — je typ představený v Swift 5.5+, který řeší problém závodů dat na úrovni jazyka. Na rozdíl od ručních zámků a front DispatchQueue actor automaticky izoluje svůj stav a synchronizuje k němu přístup. To znamená, že dvě vlákna nemohou současně změnit stejnou vlastnost typu actor, což eliminuje závod bez dalšího úsilí ze strany vývojáře. Mechanismus je založen na konceptu izolace actor, kdy kompilátor sleduje přístup k vlastnostem a metodám actor. Podle Apple, 2025 je model actor základní součástí systému konkurence Swift.
Hlavní body
Actor — je referenční typ, podobný třídě, ale s automatickou ochranou proti závodům dat. Byl představen v Swift 5.5 jako součást systému konkurence spolu s async/await a Task. Actor zaručuje, že jeho měnitelný stav nebude nikdy současně čten nebo zapisován z různých vláken bez explicitní synchronizace.
Pro deklaraci actor se používá klíčové slovo actor, za kterým následují složené závorky s jeho členy. Syntakticky actor připomíná třídu, ale chování je radikálně odlišné.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
self.balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
Kompilátor automaticky izoluje všechny vlastnosti a metody actor tak, že jsou přístupné pouze uvnitř kontextu actor. Při pokusu o přístup k balance z vnějšku actor kompilátor vyvolá chybu, pokud volání není označeno jako async.
Izolace dat — je klíčový koncept actor. Actor zaručuje vzájemně vylučný přístup ke svému stavu prostřednictvím mechanismu vykonavatele actor (actor executor). Každý actor má vlastního vykonavatele, který zpracovává všechny přístupy k jeho izolovaným členům sekvenčně.
Když kód mimo actor zavolá jeho metodu, volání je umístěno do fronty vykonavatele actor. Actor provádí pouze jeden úkol současně, což zaručuje absenci závodů. Pokud dvě vlákna současně volají deposit, druhé volání čeká na dokončení prvního.
let account = BankAccount(initialBalance: 1000.0)
// Asynchronní volání — vyžadováno z vnějšku actor
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()
Každé volání metody actor vyžaduje await, protože actor může být zaneprázdněn jiným úkolem. To není chyba, ale vědomý návrh, který zabraňuje závodům. Swift implicitně dělá gettery a settery vlastností actor asynchronními, takže čtení vlastnosti také vyžaduje await.
Actor a třída jsou referenční typy, ale jejich chování ve vícevláknovém prostředí se radikálně liší. Třída neposkytuje žádnou automatickou ochranu proti závodům, zatímco actor ji vestaví na úrovni kompilátoru prostřednictvím typového systému.
| Charakteristika | Actor | Class |
|---|---|---|
| Ochrana proti závodům | Automatická, na úrovni kompilátoru | Vyžaduje ruční synchronizaci |
| Přístup k vlastnostem | Pouze přes await z vnějšku | Přímý, bez synchronizace |
| Dědičnost | Pouze od jiných actor | Standardní třídní |
| Shoda s protokoly | Může vyhovovat protokolům | Standardní |
| Výkon | Nízké režie při izolaci | Bez synchronizace — rychlejší |
Actor může dědit pouze od jiného actor a nemůže dědit od třídy. To je provedeno vědomě, protože třída nemá mechanismus izolace actor a smíchání dvou modelů by vedlo k nedefinovanému chování.
actor SavingsAccount: BankAccount {
func applyInterest(rate: Double) {
let interest = balance * rate
balance += interest
}
}
Asynchronní volání — jsou mechanismem interakce s actor z vnějšího kódu. Protože actor izoluje svůj stav, každý přístup k jeho členům z vnějšku vyžaduje await. To umožňuje Swift zaručit, že volající kód neblokuje vlákno a actor může zpracovávat jiné požadavky.
Vedle deklarovaných typů actor Swift podporuje globální actor — atribut @MainActor, který označuje třídy, vlastnosti nebo metody jako prováděné na hlavním vlákně. To je užitečné zejména při práci s UIKit a SwiftUI.
@MainActor
class ViewModel: ObservableObject {
@Published var title: String = ""
func updateTitle() {
// Tento kód je zaručeně spuštěn na hlavním vlákně
title = "New Title"
}
}
Použití @MainActor odstraňuje nutnost ručního volání DispatchQueue.main.async, což činí kód čistším a bezpečnějším. Kompilátor kontroluje, že přepnutí na hlavní vlákno probíhá správně.
Nonisolated — je klíčové slovo umožňující označit metodu nebo počítanou vlastnost actor jako neizolovanou. Tyto členy nemají přístup k izolovanému stavu actor, ale mohoú být volány bez await z vnějšku actor.
Nonisolated metody jsou užitečné pro výpočty nezávislé na měnitelném stavu actor. Například metoda formatBalance nepřistupuje přímo k balance, pouze formátuje předanou hodnotu — takovou metodu lze bezpečně učinit nonisolated.
actor BankAccount {
private var balance: Double = 0
nonisolated func formatBalance(amount: Double) -> String {
return "$\(amount)"
}
}
Nonisolated členy jsou prováděny synchronně a nevyžadují await. Nemohou však přímo číst izolované vlastnosti actor. Pokud nonisolated metoda potřebuje hodnotu z actor, musí být předána jako parametr.
Reentrancy — je mechanismus umožňující opětovný vstup do actor během čekání na asynchronní volání. Bez reentrancy by se actor mohl navžýy zablokovat, pokud by jedna jeho metoda čekala na druhou, která na oplátku čeká na první.
Když kód uvnitř actor provádí await, actor pozastaví aktuální úkol a může zpracovat jiný zařazený do fronty. Po dokončení await je úkol obnoven. To zabraňuje deadlockům, ale vyžaduje opatrnost: stav actor mezi body await se může změnit.
actor DataProcessor {
var cache: [Int: String] = [:]
func process(id: Int) async -> String {
if let cached = cache[id] {
return cached
}
// await — bod opětovného vstupu
let result = await fetchData(id: id)
// Mezipaměť se mohla po await změnit — znovu zkontrolujte
cache[id] = result
return result
}
}
Vývojáři musí brát v úvahu reentrancy a kontrolovat stav actor po bodech await. Typickou chybou je předpoklad, že izolace actor přetrvává během asynchronních pauz. V praxi se mezi await a následujícím příkazem může stav lišit od očekávaného.
Často kladené otázky
Actor automaticky izoluje svůj stav od závodů dat, vyžaduje await pro přístup z vnějšku. Třída takovou ochranu neposkytuje — vývojář je sám zodpovědný za synchronizaci pomocí zámků nebo front. Actor dědí pouze od actor, třída od třídy.
Ano, actor může dědit od jiného actor. Podtřída získává všechny izolované vlastnosti a metody rodiče. Actor nemůže dědit od třídy, protože třídy nemají mechanismus izolace actor na úrovni kompilátoru.
Kontext actor-isolated — je oblast kódu, ve které je povolen přímý přístup k měnitelnému stavu actor. Uvnitř metod actor označených jako isolated (ve výchozím nastavení) lze vlastnosti číst a psát bez await. Kompilátor kontroluje hranice izolace.
Data z actor se předávají prostřednictvím async-metod, které vracejí typy Sendable, nebo prostřednictvím nonisolated metod přijímajících hodnoty jako parametry. Lze také vytvořit async vlastnost, která vrátí snímek stavu actor ve formě struktury Sendable.
Ano, actor může vyhovovat protokolům. Pokud protokol obsahuje izolované požadavky (actor-isolated), stanou se automaticky izolovanými pro actor. U asynchronních metod v protokolech lze pomocí markeru isolated určit, že mají být volány na konkrétním actor.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také