Undo Manager

This article provides a conceptual understanding of the basic properties and behavior of the undo manager. Practical, code-based examples are provided in later articles.

Overview

NSUndoManager is a general-purpose undo stack where clients can register callbacks to be invoked should an undo be requested. When you perform an action that changes the property values of an object (for example, by invoking a set accessor method), you can also register with an undo manager an operation that can reverse the action.

An undo manager collects all undo operations that occur within a single cycle of the run loop,so that performing an undo reverts all changes that occurred during the cycle. Also, when performing undo an undo manager saves the operations that were reverted so that you can redo the undos.

NSUndoManager is implemented as a class of the Foundation framework because executables other than applications might want to revert changes to their states. For example, you might have an interactive command-line tool with undo and redo commands; or there could be Distributed Object implementations that can revert operations “over the wire.” However, users typically see undo and redo as application-level features. The Application Kit implements undo and redo in its NSTextView object and makes it easy to implement it in objects along the responder chain. For more on the role of the Application Kit in undo and redo, see Using Undo in AppKit-Based Applications.

Undo Operations and Groups

An undo operation is a method for reverting a change to an object, along with the arguments needed to revert the change. The operation specifies:

Because NSUndoManager also supports redo, these operations should typically be reversible. The method that’s invoked during an undo operation should itself register an undo operation that will then serve as the redo action.

Undo operations are typically collected in undo groups, which represent whole revertible actions, and are stored on a stack. When an undo manager performs undo or redo, it is actually undoing or redoing an entire group of operations. For example, a user could change the type face and the font size of some text. An application might package both attribute-setting operations as a group, so when the user chooses Undo, both type face and font size are reverted. To undo a single operation, it must still be packaged in a group.

Redo operations and groups are simply undo operations stored on a separate stack (described below).

NSUndoManager normally creates undo groups automatically during the run loop. The first time it is asked to record an undo operation in the run loop, it creates a new group. Then, at the end of the loop, it closes the group. You can create additional, nested undo groups within these default groups using the beginUndoGrouping and enableUndoRegistration methods. You can also turn off the default grouping behavior using setGroupsByEvent:.

The Undo and Redo Stacks

Undo groups are stored on a stack, with the oldest groups at the bottom and the newest at the top. The undo stack is unlimited by default, but you can restrict it to a maximum number of groups using the setLevelsOfUndo: method. When the stack exceeds the maximum, the oldest undo groups are dropped from the bottom.

Initially, both stacks are empty. Recording undo operations adds to the undo stack, but the redo stack remains empty until undo is performed. Performing undo causes the reverting operations in the latest group to be applied to their objects. Since these operations cause changes to the objects’ states, the objects presumably register new operations with the undo manager, this time in the reverse direction from the original operations. Since the undo manager is in the process of performing undo, it records these operations as redo operations on the redo stack. Consecutive undos add to the redo stack. Subsequent redo operations pull the operations off the redo stack, apply them to the objects, and push them back onto the undo stack.

The redo stack’s contents last as long as undo and redo are performed successively. However, because applying a new change to an object invalidates the previous changes, as soon as a new undo operation is registered, any existing redo stack is cleared. This prevents redo from returning objects to an inappropriate prior state. You can check for the ability to undo and redo with the canUndo and canRedo methods.