Swift, actor: Actor-isolated property 'scanning' can not be mutated from a non-isolated context
Asked Answered
S

3

43

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?

Styrax answered 20/9, 2021 at 18:43 Comment(1)
Create a function within your actor that updates the property. Then MyObj should call that function.Jadwiga
K
56

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).

Kilohertz answered 20/9, 2021 at 18:47 Comment(6)
ture. I needed to wrap that in a method in StatesActor.... thank you.Styrax
Basically the actor is teaching you how to use multithreading safely.Kilohertz
Async property setters are in theory possible. Swift just doesn't have them yet.Ongoing
await MainActor.run { ... set your lovely property here ... }Aciculum
Up until today, when I installed Xcode 14b4, I was able to write await myActor.foo = bar without issue.Sitology
I had a non-actor model with a @MainActor property so this worked: Task { @MainActor in CODE }Thorr
K
8

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)
}
Kanara answered 18/11, 2022 at 18:27 Comment(0)
C
4

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 
      }
    }
}
Capstan answered 17/2, 2024 at 21:9 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.