NSInvocation in Practice
There’ve been quite a few mentions of NSInvocation recently. I have to agree about how useful it can be, and wanted to mention a few specific places I used it in Lexicon.
Animation
In Lexicon’s Practice mode, as you flip and view cards they animate (using Quartz Composer).
![]()
While one animation is running, I don’t want the user to be able to start another one, but of course the app should be responsive during the transitions. So I decided that if they click a button or press a key during an animation, I’d save the action and wait to perform it until the current animation ended. There are quite a few actions that can be done, and I wanted some efficient way to store which to execute. This is a perfect case for the Command Pattern, NSInvocation in Cocoa’s case. This is what the flip action looks like (simplified somewhat):
- (BOOL)tryAction:(SEL)action withSender:(id)sender;
{
if (![self isAnimating])
return YES;
[invocation release];
invocation = [[NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:action]] retain];
[invocation setSelector:action];
[invocation setTarget:self];
[invocation setArgument:&sender atIndex:2];
return NO;
}
- (IBAction)flipPractice:(id)sender;
{
if (![self tryAction:_cmd withSender:sender])
return;
// actual flip actions...
}
- (IBAction)restartPractice:(id)sender;
{
if (![self tryAction:_cmd withSender:sender])
return;
// actual restart actions...
}
This takes advantage of the hidden _cmd variable to automatically get the current method. The _cmd variable is the selector for the current method, just like self is the object on which the method is being called.
The rest is pretty straightforward-if the animation is running, save an invocation of the action, otherwise just execute it now. When the animation ends I just check if there is an invocation, and if so tell it to execute. This helped simplify development a lot, as I could easily make new methods animation-safe by just adding 2 lines of code.
Tutorial
Another place where I used invocations is Lexicon’s Tutorial system (the help system you see when you first run Lexicon). This learning system is pretty important to teaching users how to use Lexicon. One of the most important features of the Tutorial system is that in addition to showing you text about Lexicon, it acts things out in the UI so you can actually see what is happening step by step. This kind of delayed action is easy to do (with NSTimer or NSObject’s performSelector:withObject:afterDelay: method), but the tricky part is that the user can end the tutorial or skip the current step, in which case tracking down those delayed methods (and especially their targets) is quite tricky. The solution, of course, is NSInvocation again.
One of the Tutorial classes implements the method:
- (id)prepareWithInvocationTarget:(id)target
delay:(NSTimeInterval)delay;
which is similar to NSUndoManager’s prepareWithInvocationTarget. Then, using the power of dynamic message forwarding, the Tutorial saves the proper NSInvocation for the target and method and prepares to execute it after the requested delay. If the user cancels the Tutorial step, then all of its invocations are stopped and discarded (which is now easy since they now all go through the same object). This makes the Tutorial system much more robust and user-friendly.
As you can see, NSInvocation’s dynamic nature makes it very easy to add interesting features to applications in Cocoa.