In Objective-C, messaging a nil
reference is a no-op, so if we consider the following class:
The following code snippet will not print anything, but it also won’t crash:
Since Objective-C is a dynamic language, we can represent message invocations with objects and invoke them at runtime. NSInvocation
makes that easy, and it also respects the convention that messaging a nil
object is a no-op:
Again, the code snippet above won’t print anything, but it also won’t crash.
This also holds for weak references. If steve
were a weak reference that got nil
-ified before the invocation was dispatched, this code would fail gracefully. Here’s an example for completeness:
The code above also doesn’t print anything, since steveWeak
gets nil
-ified when steve
is assigned to nil
.
Now onto NSUndoManager
.
If you’re not familiar with NSUndoManager
, it is the primary class used when implementing undo support in Cocoa and Cocoa Touch apps. Every time you perform an action that could be un-done, you register it with an instance of NSUndoManager
. The “action” is simply a method (or block) that gets invoked when the user presses “Undo”. Here’s a basic example that registers our -undoSomething
method, which reverses whatever -doSomething:
does:
Actions are stored and invoked in LIFO (last-in first-out) order, so if we were to press “Undo” now, -[NSUndoManager undo]
would end up invoking -undoSomething
.
In fact, if you look at the stack trace at the time -undo
is invoked, you’ll notice NSUndoManager
internally simply keeps around a stack of NSInvocation
s to dispatch with each undo operation.
If you take a look at the documentation for -prepareWithInvocationTarget:
, you’ll notice it claims the “undo manager maintains a weak reference to the target”. As I learned the hard way with some new crashes in Pixen, that is actually not true.
Consider the following example:
Since steve
falls out-of-scope before dispatch_after
calls -[NSUndoManager undo]
, its pointee gets deallocated. When the call to undo
is finally made, we get a crash, and if we turn on Zombie Objects in the Scheme Editor, we can confirm NSUndoManager
is trying to talk to a deallocated object:
This crash would not occur if NSUndoManager
kept around a weak reference to our Person
since it would have been nil
-ified, and as we convinced ourselves above, sending an NSInovcation
to a nil
target is perfectly safe. Thus, contrary to the documentation, NSUndoManager
does not actually store weak references to undo targets.