Swift actor isolation is a feature of Swift concurrency that allows actors to protect their state from concurrent access by other actors or threads. Actor isolation ensures that only one actor can access its own state at a time, and that any communication with other actors is done asynchronously through messages. This helps prevent data races and other concurrency bugs.
By default, all properties and methods of an actor are isolated to that actor, which means they can only be accessed from within the actor or by using await
from outside the actor. There are some exceptions to actor isolation, such as accessing immutable state or using nonisolated
keywords to mark methods that can be safely called from outside the actor.
actor Employee {
let employeeID: Int
var salary: Double
init(employeeID: Int, initialSalary: Double) {
self.employeeID = employeeID
self.salary = initialSalary
}
func increaseSalary(amount: Double) {
// Accessed from within the actor without await
assert(amount >= 0)
salary += amount
}
}
The isolated
and nonisolated
keywords in Swift are used to control the actor isolation of functions and properties.
The isolated
keyword can only be used on function parameters that are actor types. This means that the function can access the actor’s state without using await
, as if it were part of the actor itself.
func giveRaise(to employee: isolated Employee, amount: Double) async {
employee.increaseSalary(amount: amount)
}
A given function cannot have multiple isolated parameters:
// warning: cannot have more than one 'isolated' parameter; this is an error in Swift 6.
func f(a: isolated Employee, b: isolated Employee) {
// ...
}
The isolated keyword cannot be used on properties or methods of actors. For example:
actor Employee {
/*...*/
// error: 'isolated' may only be used on 'parameter' declarations
isolated var x = 0
// error: 'isolated' may only be used on 'parameter' declarations
isolated func baz() {
// do something
}
}
Sometimes you may want to mark some properties or methods as nonisolated
, which means they can be accessed without await
from any context. This is useful when the property or method does not depend on or affect the mutable state of the actor.
actor Employee {
// ...
nonisolated let age: Int
// A nonisolated method that can be called from any context
nonisolated func doSomething() {
print("Do something without touch actor state")
}
}
// A global function that takes an actor as a parameter
func test(_ employee: Employee) async {
// Call the nonisolated method without await
employee.doSomething()
// Call the isolated method with await
await counter.increaseSalary(amount: 10)
}
Non-isolated functions don’t have access to actor state, so they are free to satisfy synchronous protocol requirements of any kind. For example:
extension Employee: Hashable {
nonisolated func hash(into hasher: inout Hasher) {
hasher.combine(employeeID)
}
}