问题
I am having trouble understanding how NSUndoManager captures values when the values are reference types. Whenever try to use the undoManager, the values don't undo.
Note: I need to support macOS 10.10, and I am using Swift 3 and XCode 8
Here I save the state of the ID numbers, reset them all to -1, and then try to undo. The result is that they are all still -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
}
}
To prove to myself that it was a problem capturing values, instead of saving the ID objects themselves, I stored the Int values that they contain. It worked perfectly as the Ints are value types.
Then I read some more about closure capturing and tried this.
undoManager?.registerUndo(withTarget: self, handler: { [saved = savedIDs]
(self) -> () in
self.undo(savedIDs: saved)
})
Same problem, all still -1.
回答1:
NSUndoManager
isn't really about capturing about old values, but about capturing operations that restore the original values.
You are only capturing references to the ID
s. So the elements in savedIDs
are pointing to the same objects as the id
properties on the elements of people
, i.e. when you change one, you also change the other.
What you need to do is manually save the ID
s and the values you want to reset them to, like so:
let savedPairs = people.map { (id: $0.id, originalValue: $0.id.number) }
This ensures that the number
value itself of id
is saved somewhere, not just the pointer to the id
.
You can then register an action that performs the undoing with the values you captured manually, like so:
undoManager.registerUndo(withTarget: self) {
savedPairs.forEach { $0.id.number = $0.originalValue }
}
来源:https://stackoverflow.com/questions/41575694/nsundomanager-capturing-reference-types-possible