In order to conform Actors to protocols with properties, Swift should implement an async setter which would allow an actor to conform to a protocol of the following form:
protocol SessionService {
var user: User? { async get; async set }
}
At the moment, it is impossible for an actor to conform to a protocol of this form:
protocol SessionService {
var user: User? { get set }
}
This is a common protocol pattern, and seems like a big functionality that is missing from actors. Instead, you end up changing the protocol to something like this:
This feels like a few steps backwards, when Actors are meant to be a step forward.
In order to solve this, I propose implementing an async setter, which would indicate that you must await when you set the property. The syntax could be something similar to: await sessionService.user = user.
The reason actors don't allow you to set individual properties asynchronously from outside the actor is that it's almost certainly not a good idea to actually write code that way. You need to be thinking more transactionally than that and applying all of the changes you want to make in one "turn" on the actor.
Actors perhaps revive the object-oriented notion of encapsulation. The premise is that the actor holds oodles of data with invariants about mutual state (e.g., a count variable to avoid traversing a tree). So it's really the actor's job to manage setting state, and the actor would/should prevent any client from operations that could invalidate those invariants or see incomplete states (hence, encapsulation). The client just sends messages and data as needed.
I would’ve asked what is the goal of such actor in the first place? If it acts as a mutex, then maybe you simply need a mutex here… Or review from where this properties are modified (some global actor, probably?) and isolate a class on the same global actor. Actor is intended to encapsulate state and transitions of this state.
If you define your mutable properties within a nested struct you can then use an inout closure to mutate it atomically.
// Inside actor, or any type.
struct MutableState {
var one = 1
var two = 2
}
var mutableState = MutableState()
func mutate(_ mutate: (inout MutableState) -> Void) {
mutate(&mutableState)
}
// Usage
mutate { mutableState in
mutableState.one = 11
mutableState.two = 22
}
Of course, offering a concurrency-safe version of this API is left as an exercise for the reader, but the enhancements to isolation analysis in Swift 6 may finally allow a nice API surface.