Reputation: 20138
I have an actor:
actor StatesActor {
var job1sActive:Bool = false
...
}
I have an object that uses that actor:
class MyObj {
let myStates = StatesActor()
func job1() async {
myStates.job1IsActive = true
}
}
Line:
myStates.job1IsActive = true
errors out with this error:
Actor-isolated property 'job1IsActive' can not be mutated from a non-isolated context
How can I use an actor to store/read state information correctly so the MyObj can use it to read and set state?
Upvotes: 51
Views: 53290
Reputation: 51
You can use more generic method that allows you to modify property in any way you want within one call on actor isolated level.
extension Actor {
func isolated<T: Sendable>(_ closure: (isolated Self) -> T) -> T {
return closure(self)
}
}
And use it
actor StatesActor {
var job1IsActive: Bool = false
}
class MyObj {
let myStates = StatesActor()
func job1() async {
await myStates.isolated {
// this call will be done on actor isolated level
$0.job1IsActive = false
}
}
}
Upvotes: 5
Reputation: 3794
It is not allowed to use property setters from code that is not run on the actor ("actor isolated code" is the exact terminology). The best place to mutate the state of an actor is from code inside the actor itself. In your case:
actor StatesActor {
var job1IsActive: Bool = false
func startJob1() {
job1IsActive = true
...
}
}
class MyObj {
let myStates = StatesActor()
func job1() async {
await myStates.startJob1()
}
}
Async property setters are in theory possible, but are unsafe. Which is probably why Swift doesn't have them. It would make it too easy to write something like if await actor.a == nil { await actor.a = "Something" }
, where actor.a
can change between calls to the getter and setter.
The answer above works 99% of the time (and is enough for the question that was asked). However, there are cases when the state of an actor needs to be mutated from code outside the actor. For the MainActor
, use MainActor.run(body:)
. For global actors, @NameOfActor
can be applied to a function (and can be used to define a run
function similar to the MainActor
). In other cases, the isolated
keyword can be used in front of an actor parameter of a function:
func job1() async {
await myStates.startJob1()
let update: (isolated StatesActor) -> Void = { states in
states.job1IsActive = true
}
await update(myStates)
}
Upvotes: 10
Reputation: 534893
How can I use an actor to store/read state information correctly so the MyObj can use it to read and set state?
You cannot mutate an actor's instance variables from outside the actor. That is the whole point of actors!
Instead, give the actor a method that sets its own instance variable. You will then be able to call that method (with await
).
Upvotes: 66