I have code where a set of functions need to run under a global actor context, but have undo capability. When I call them I get the following compiler warning (Xcode 14).
Non-sendable type 'UndoManager?' passed in call to global actor 'MyActor'-isolated function cannot cross actor boundary
I understand why I get this warning, but not how to resolve it.
Here is some example code to illustrate the problem.
@globalActor actor MyActor {
static let shared = MyActor()
}
class MyClass {
@MyActor func doSomething(undoManager: UndoManager?) {
// Do something here
undoManager?.registerUndo(withTarget: self) {
$0.reverseSomething(undoManager: undoManager)
}
}
@MyActor func reverseSomething(undoManager: UndoManager?) {
// Do the reverse of something here
undoManager?.registerUndo(withTarget: self) {
$0.doSomething(undoManager: undoManager)
}
}
}
struct MyView: View {
@Environment(\.undoManager) private var undoManager: UndoManager?
let myObject: MyClass
var body: some View {
Button("Do something") { myObject.doSomething(undoManager: undoManager) } // <- Warning here: Non-sendable type 'UndoManager?' passed in call to global actor 'MyDBActor'-isolated function cannot cross actor boundary
}
}
What you are trying to do is keep the registration of the Undo/Redo synchronous with the actual work being done (or undone).
But you've also isolated your model into its own Actor. Messages can only be passed between actors asynchronously . Swift is going to force you to deal explicitly with that.
To synchronize with Actors, the synchronization, in this case, would happen by awaiting on a message sent to MyActor
to do the work, and then awaiting on the main actor to register the undo/redo.
So you're going to have to spawn a task to handle synchronization.
In the example below I've done that. I create a task that awaits the work and then awaits the registration of the do/undo.
The problem my code introduces is that that now that whole task is asynchronous. When you call "doSomething" you're not actually doing it... you're asking the main actor to do it at some point in the future. This may, or may not, cause problems with your code.
import SwiftUI
@globalActor actor MyActor {
static let shared = MyActor()
func reallyDoSomething() {
}
func reallyUndoSomething() {
}
}
class MyClass {
@MainActor func doSomething(undoManager: UndoManager) {
Task {
// Do the reverse of something here
await MyActor.shared.reallyDoSomething()
await MainActor.run {
undoManager.registerUndo(withTarget: self) { this in
this.reverseSomething(undoManager: undoManager)
}
}
}
}
@MainActor func reverseSomething(undoManager: UndoManager) {
Task {
// Do the reverse of something here
await MyActor.shared.reallyUndoSomething()
await MainActor.run {
undoManager.registerUndo(withTarget: self) { this in
this.doSomething(undoManager: undoManager)
}
}
}
}
}
struct MyView: View {
@Environment(\.undoManager) private var undoManager: UndoManager?
let myObject: MyClass
var body: some View {
Button("Do something") {
if (nil != undoManager) {
myObject.doSomething(undoManager: undoManager!)
}
} // <- Warning here: Non-sendable type 'UndoManager?' passed in call to global actor 'MyDBActor'-isolated function cannot cross actor boundary
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.