Swift 中基于调用的撤消管理器

Invocation-based Undo Manager in Swift

我在 Swift 中采用更复杂的基于调用的方法来撤消注册时遇到了麻烦(基于 NSHipster 文章 here。Apple 的文档仍然包含 [=31= 中的所有示例代码],并且调用设置的语义非常不同)。

我的 NSDocument 子类 Document 具有以下对模型对象进行操作的方法,我希望它可以撤消:

func rename(object: Any, to newName: String) {
    // This is basically a protocol that requires implementing:
    // var name: String { get set }
    //  
    guard var namedObject = object as? EditorHierarchyDisplayable else {
        return
    }

    // Register undo:
    let undoController = undoManager?.prepare(withInvocationTarget: self) as? Document
    undoController?.rename(object: namedObject, to: namedObject.name)
    undoManager?.setActionName("Rename \(namedObject.localizedClassName)")

    // Perform the change:
    namedObject.name = newName
}

我发现上面的 undoControllernil,因为尝试转换为 Document 失败了。如果我删除演员表(并注释掉对 undoController.rename(... 的调用),prepare(withInvocationTarget:) returns 以下对象:

(lldb) print undoController
(Any?) $R0 = some {
 payload_data_0 = 0x00006080000398a0
 payload_data_1 = 0x0000000000000000
 payload_data_2 = 0x0000000000000000
 instance_type = 0x000060800024f0d8
}
(lldb) print undoController.debugDescription
(String) $R1 = "Optional(NSUndoManagerProxy)"
(lldb) 

我错过了什么?

我认为基本的混淆是 prepare(withInvocationTarget:) returns 一个 代理对象 (恰好是撤消管理器本身,但这是一个实现细节).这个想法是你向这个代理对象发送你发送的相同消息来撤消操作,而不是执行它们(因为它不是实际的对象),它在内部捕获这些调用并保存它们以备后用。

所以你的代码真的应该像这样开始:

let selfProxy: Any = undoManager?.prepare(withInvocationTarget: self)

这在 Objective-C 中非常有效,因为 "catchall" 类型 (id) 的类型检查非常宽松。但是 Swift 中的等效 Any class 更加严格,并且不适合相同的技术,如果有的话。