Swift NSUndoManager:是否可以捕获引用类型?

Swift NSUndoManager:是否可以捕获引用类型?,swift,cocoa,nsundomanager,Swift,Cocoa,Nsundomanager,当值是引用类型时,我很难理解NSUndoManager如何捕获值。无论何时尝试使用undoManager,值都不会撤消 注意:我需要支持macOS 10.10,我使用的是Swift 3和XCode 8 在这里,我保存ID号的状态,将它们全部重置为-1,然后尝试撤消。结果是它们仍然是-1 import Cocoa class Person: NSObject { var id: ID init(withIDNumber idNumber: Int) { self

当值是引用类型时,我很难理解NSUndoManager如何捕获值。无论何时尝试使用undoManager,值都不会撤消

注意:我需要支持macOS 10.10,我使用的是Swift 3和XCode 8

在这里,我保存ID号的状态,将它们全部重置为-1,然后尝试撤消。结果是它们仍然是-1

import Cocoa

class Person: NSObject {
    var id: ID 
    init(withIDNumber idNumber: Int) {
        self.id = ID(idNumber)
    }
}    

class ID: NSObject {
    var number: Int
    init(_ number: Int) {
        self.number = number
    }
}

ViewController... {

    @IBAction func setIDsToNegative1(_ sender: NSButton) {
        var savedIDs: [ID] = []
        for person in people {
            savedIDs.append(person.id)
        }

        undoManager?.registerUndo(withTarget: self, selector:#selector(undo(savedIDs:)), object: savedIDs)

        for person in people {
            person.id.number = -1
        }
    }


   func undo(savedIDs: [ID]) {
         for id in savedIDs {
              print(id.number)
         }
         //Prints -1, -1, -1
    }

}
为了向自己证明捕获值是个问题,我没有保存ID对象本身,而是存储了它们包含的Int值。它工作得很好,因为int是值类型

然后我读了更多关于闭包捕获的内容,并尝试了这个方法

undoManager?.registerUndo(withTarget: self, handler: { [saved = savedIDs] 
    (self) -> () in 
         self.undo(savedIDs: saved)
 })

同样的问题,仍然是-1。

NSUndoManager
并不是捕获旧值,而是捕获恢复原始值的操作

import Cocoa

class Person: NSObject {
    var id: ID 
    init(withIDNumber idNumber: Int) {
        self.id = ID(idNumber)
    }
}    

class ID: NSObject {
    var number: Int
    init(_ number: Int) {
        self.number = number
    }
}

ViewController... {

    @IBAction func setIDsToNegative1(_ sender: NSButton) {
        var savedIDs: [ID] = []
        for person in people {
            savedIDs.append(person.id)
        }

        undoManager?.registerUndo(withTarget: self, selector:#selector(undo(savedIDs:)), object: savedIDs)

        for person in people {
            person.id.number = -1
        }
    }


   func undo(savedIDs: [ID]) {
         for id in savedIDs {
              print(id.number)
         }
         //Prints -1, -1, -1
    }

}
您仅捕获对
ID
s的引用。因此,
savedIDs
中的元素指向与
people
元素上的
id
属性相同的对象,即当您更改一个时,您也会更改另一个

您需要做的是手动保存
ID
s以及要将其重置为的值,如下所示:

let savedPairs = people.map { (id: $0.id, originalValue: $0.id.number) }
undoManager.registerUndo(withTarget: self) {
    savedPairs.forEach { $0.id.number = $0.originalValue }
}
这样可以确保
id
number
值本身保存在某个位置,而不仅仅是指向
id
的指针

然后,您可以注册一个操作,该操作使用手动捕获的值执行撤消操作,如下所示:

let savedPairs = people.map { (id: $0.id, originalValue: $0.id.number) }
undoManager.registerUndo(withTarget: self) {
    savedPairs.forEach { $0.id.number = $0.originalValue }
}

谢谢,这证实了我的猜测,“冷冻干燥”参考状态是不可能的。在我提供的示例中,数据结构非常简单,因此保存元组很容易。但是,如果我想撤销由其他引用类型组成的更复杂数据结构的类呢?我必须深入研究对象结构并提取所有的值类型,不是吗?我认为像我在这里建议的那样做要容易得多:你可以这样做,但我认为你必须管理每个对象的撤消状态,特别是当你处理对象层次结构时,你会受到很大的伤害。请记住,每个属性都需要一个撤消堆栈。另外,在这种情况下,这甚至没有帮助,因为您需要对每个ID对象调用undo()。考虑尽可能多地为模型使用值类型,这些值会像预期的那样捕获值。我们所能做的就是按照NSCopying协议获取处于先前状态的对象,并执行深度复制以恢复该状态,并且在向撤消堆栈添加数据时,只需对原始状态进行深度复制并添加到撤消堆栈