Skip to content

UXUndoOp

UXUndoOp is one registered inverse: the call that would undo a single model change.

#use <UXKit> // or #import "UXUndoManager.xc"
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 two fields are held in different ways:

blocka callback — never owns its receiver
arga 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.

callback b void(Object* arg) = op.block;
if (b !=0) { b(op.arg); } // auto-zeroed when the receiver died

A 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.

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 redo
undo.redo(); // calls setX(newX); which registers setX(oldX) again

UXUndoOp has two fields, and undo/redo is one machine run in opposite directions. For you this results in a rule, not an API:

callback block void(Object* arg)

The inverse call. Null is allowed and does nothing.

Object* arg

The 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.

  • A plain class (not an Object subclass)