Swift 5.5 is introducing "actors" into the language. This will allow Swift to support the async
and await
keywords, which is a major step forward for concurrency in Swift.
What is the actor model, and how does it relate to concurrency?
An actor
is an entity similar to an object in an object-oriented programming language. Actors are initialized, can hold data, and define instance methods. However, in addition to these capabilities, actors have a few tools to help with concurrency.
- Local state held by an actor is limited to a single concurrency domain. Each actor protects its own data through data isolation, ensuring that only a single thread will access that data at a given time, even when many clients are concurrently making requests of the actor.
- Actors can define operations that other actors can call to modify its local state in a thread-safe, asynchronous manner. Actors are only allowed to modify their own internal state — they can't directly modify the properties stored on another actor.
So in short, an actor is like an object that protects its mutable state from being arbitrarily modified across different threads.
Within Swift
In the Swift Concurrency Manifesto, Chris Lattner writes:
As a Swift programmer, it is easiest to think of an actor as a combination of a DispatchQueue
, the data that queue protects, and messages that can be run on that queue. Because they are embodied by an (internal) queue abstraction, you communicate with Actors asynchronously, and actors guarantee that the data they protect is only touched by the code running on that queue. This provides an "island of serialization in a sea of concurrency".
Pulling out a few key sections from the proposal:
An actor is a reference type that protects access to its mutable state, and is introduced with the keyword actor
:
actor BankAccount {
let accountNumber: Int
var balance: Double
init(accountNumber: Int, initialDeposit: Double) {
self.accountNumber = accountNumber
self.balance = initialDeposit
}
}
Like other Swift types, actors can have initializers, methods, properties, and subscripts. They can be extended and conform to protocols, be generic, and be used with generics... The primary difference is that actors protect their state from data races. This is enforced statically by the Swift compiler through a set of limitations on the way in which actors and their instance members can be used, collectively called actor isolation.
Cross-actor references to an actor property are permitted as an asynchronous call so long as they are read-only accesses:
func checkBalance(account: BankAccount) {
print(await account.balance) // okay
await account.balance = 1000.0 // error: cross-actor property mutations are not permitted
}