Actor / Preventing Data Races
In Swift, an actor is a type that prevents data races on shared mutable state. Access to an actor's properties and methods is automatically serialized.
Syntax
// Defining an actor (similar syntax to a class)
actor ActorName {
var state: Type
init(initialValue: Type) {
self.state = initialValue
}
// Methods inside an actor are automatically isolated
func method() {
state = newValue
}
}
// Accessing an actor requires await
let result = await actorInstance.method()
// @MainActor: guarantees execution on the main thread
@MainActor
func updateUI() {
// Update UI
}
Syntax Overview
| Syntax | Description |
|---|---|
| actor Name { } | Defines an actor. The syntax is similar to a class, but actors do not support inheritance. |
| await actor.method() | Calling an actor's method from outside requires await. |
| nonisolated func | Defines a method that is excluded from actor isolation (only for thread-safe operations). |
| @MainActor | Ensures that a class, function, or property runs on the main thread. |
| MainActor.run { } | Runs arbitrary code on the main thread. |
Sample Code
import Foundation
// A thread-safe counter using an actor
actor Counter {
private var count = 0
func increment() {
count += 1
}
func getCount() -> Int {
return count
}
// nonisolated: a method that does not need actor isolation
nonisolated func description() -> String {
return "Counter actor"
}
}
let counter = Counter()
// Increment 1000 times concurrently
await withTaskGroup(of: Void.self) { group in
for _ in 1...1000 {
group.addTask {
await counter.increment()
}
}
}
let total = await counter.getCount()
print("Final count: \(total)") // Always 1000
// nonisolated methods do not require await
print(counter.description())
// @MainActor: update the UI on the main thread
@MainActor
class ViewModel {
var title: String = "Loading..."
func loadData() async {
// Process in the background
let result = await Task.detached { "Data loaded" }.value
// @MainActor allows direct UI updates
title = result
print("Title: \(title)")
}
}
Task {
let vm = await ViewModel()
await vm.loadData()
}
Notes
With a regular class, accessing a property from multiple threads simultaneously can cause data races. Using an actor, access is automatically serialized, making it safe.
An actor is similar to a class but does not support inheritance. It can conform to protocols. From inside an actor, you can access its own properties and methods without await, but access from outside always requires await.
For the basics of async/await, see async / await basics.
If you find any errors or copyright issues, please contact us.