SE-0306 introduced actors, which are conceptually similar to classes that are safe to use in concurrent environments. This is possible because Swift ensures that mutable state inside your actor is only ever accessed by a single thread at any given time, which helps eliminate a variety of serious bugs right at the compiler level.
To demonstrate the problem actors solve, consider this Swift code that creates a RiskyCollector
class able to trade cards from their deck with another collector:
class RiskyCollector {
var deck: Set<String>
init(deck: Set<String>) {
self.deck = deck
}
func send(card selected: String, to person: RiskyCollector) -> Bool {
guard deck.contains(selected) else { return false }
deck.remove(selected)
person.transfer(card: selected)
return true
}
func transfer(card: String) {
deck.insert(card)
}
}
In a single-threaded environment that code is safe: we check whether our deck contains the card in question, remove it, then add it to the other collector’s deck. However, in a multi-threaded environment our code has a potential race condition, which is a problem whereby the results of the code will vary as two separate parts of our code run side by side.
If we call send(card:to:)
more than once at the same time, the following chain of events can happen:
In that situation one player loses a card while the other gains two cards, and if that card happened to be a Black Lotus from Magic the Gathering then you’ve got a big problem!
Actors solve this problem by introducing actor isolation: stored properties and methods cannot be read from outside the actor object unless they are performed asynchronously, and stored properties cannot be written from outside the actor object at all. The async behavior isn’t there for performance; instead it’s because Swift automatically places these requests into a queue that is processed sequentially to avoid race conditions.
So, we could rewrite out RiskyCollector
class to be a SafeCollector
actor, like this:
actor SafeCollector {
var deck: Set<String>
init(deck: Set<String>) {
self.deck = deck
}
func send(card selected: String, to person: SafeCollector) async -> Bool {
guard deck.contains(selected) else { return false }
deck.remove(selected)
await person.transfer(card: selected)
return true
}
func transfer(card: String) {
deck.insert(card)
}
}
There are several things to notice in that example:
actor
keyword. This is a new concrete nominal type in Swift, joining structs, classes, and enums.send()
method is marked with async
, because it will need to suspend its work while waiting for the transfer to complete.transfer(card:)
method is not marked with async
, we still need to call it with await
because it will wait until the other SafeCollector
actor is able to handle the request.To be clear, an actor can use its own properties and methods freely, asynchronously or otherwise, but when interacting with a different actor it must always be done asynchronously. With these changes Swift can ensure that all actor-isolated state is never accessed concurrently, and more importantly this is done at compile time so that safety is guaranteed.
Actors and classes have some similarities:
self
and therefore don’t get isolated.Beyond actor isolation, there are two other important differences between actors and classes:
final
keyword, and more. This might change in the future.Actor
protocol; no other concrete type can use this. This allows you to restrict other parts of your code so it can work only with actors.The best way I’ve heard to explain how actors differ from classes is this: “actors pass messages, not memory.” So, rather than one actor poking directly around in another’s properties or calling their methods, we instead send a message asking for the data and let the Swift runtime handle it for us safely.
SAVE 50% To celebrate WWDC23, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Download all Swift 5.5 changes as a playground Link to Swift 5.5 changes
Link copied to your pasteboard.