UXUndoOp
UXUndoOp is one registered inverse: the call that would undo a single model
change.
#use <UXKit> // or #import "UXUndoManager.xc"Overview
Section titled “Overview”class UXUndoOp { callback block void(Object* arg); // what to call Object* arg; // what to pass it}registerUndo makes one and
puts it in the open UXUndoGroup.
undo.registerUndo(&self.setX, box(oldX)); // "to undo, set x back to oldX"The asymmetry is the design
Section titled “The asymmetry is the design”The two fields are held in different ways:
block | a callback — never owns its receiver |
arg | a strong reference — the data to restore |
The target is not retained because a model that owns its undo manager would
otherwise form a retain cycle: model → manager → group → op → model.
NSUndoManager does not retain targets either, for the same reason.
The argument is retained because it is the record’s content. The old value must survive until someone presses Undo, possibly long after the object it came from has changed.
An undo stack keeps data alive and does not keep objects alive.
A dead target is a silent skip
Section titled “A dead target is a silent skip”callback b void(Object* arg) = op.block;if (b != …0) { b(op.arg); } // auto-zeroed when the receiver diedA callback auto-zeroes when its receiver is deallocated. An operation whose model object has gone does nothing, and the rest of the group still runs.
Undoing a change to a document that has since been closed does not crash, and does not prevent the other changes in that group from being undone.
As a result, an undo can accomplish nothing without reporting it. If your model objects can die while their undo records live, this is the cause, and it is not a fault in the stack.
Why undo and redo need no second machine
Section titled “Why undo and redo need no second machine”An op only ever holds the inverse. There is no redo field, because putting things back is itself a change: when the block runs, the model registers its own inverse, and the manager routes that registration to the other stack.
undo.undo(); // calls setX(oldX); setX registers setX(newX) as the redoundo.redo(); // calls setX(newX); which registers setX(oldX) againUXUndoOp has two fields, and undo/redo is one machine run in opposite
directions. For you this results in a rule, not an API:
Fields
Section titled “Fields”callback block void(Object* arg)The inverse call. Null is allowed and does nothing.
Object* argThe data to restore, retained. Null is fine for an inverse that needs no argument, such as “re-show the panel”.
Because it is an Object*, a primitive must be boxed. The box is a copy of the
old value taken at registration time, so the record does not track later
changes to its source.
Conforms to
Section titled “Conforms to”- A plain class (not an
Objectsubclass)
See also
Section titled “See also”UXUndoGroup: the ops that undo togetherUXUndoManager: the stacks- Bound methods and callbacks: why a dead target is a silent skip