简体   繁体   中英

How to resolve - compiler warning that UndoManager is non-sendable

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM