NSInvocation
An NSInvocation object takes an Objective-C message and turns it into an object. The invocation object stores the message's receiver (called the target in invocation-speak), selector, and arguments. An invocation object can be saved for later execution or passed on to another part of your code. When you send the invocation object an invoke message, the invocation sends the target object a message using the stored selector and arguments.
As an example, consider a LineGraphic class with a method drawWithColor:width: that draws a line with a specified color and line width:
LineGraphic *graphic = ... [graphic drawWithColor: [NSColor redColor] width: 2.0];
Listing 16.2 shows how to turn the preceding message into an invocation.
Example 16.2. Constructing an NSInvocation
LineGraphic *graphic = ... NSInvocation *drawInvocation = [NSInvocation invocationWithMethodSignature: [graphic methodSignatureForSelector: @selector(drawWithColor:width:)]]; [drawInvocation setTarget: graphic]; [drawInvocation setSelector: @selector(drawWithColor:width:)]; [drawInvocation retainArguments]; NSColor *color = [NSColor redColor]; float linewidth = 2.0; [drawInvocation setArgument: &color atIndex: 2]; [drawInvocation setArgument: &linewidth atIndex: 3];
To set up an invocation, NSInvocation needs to know the return type of the message being encapsulated and the types of the message's arguments. The message's selector is just a name and doesn't carry any type information, so you must obtain the type information by calling the target's methodSignatureForSelector:. This is a method that all classes inherit from NSObject. It returns an NSMethodSignature object, which is an encoded representation of the selector's return type and argument types. Finally, you pass the returned NSMethodSignature to NSInvocation's invocationWithMethodSignature: class method to create the invocation.
Next, you set the invocation's target and selector with setTarget: and setSelector:.
An NSInvocation does not retain its target or any of its arguments by default. If you are going to save an invocation for future execution, you should ask the invocation to retain its target and arguments by sending the invocation a retainArguments message. This prevents the target and arguments from being released before the invocation is invoked.
The arguments for the encapsulated message are set with the setArgument:atIndex: method:
- You pass the address of the variable being used for the argument, not the variable itself. You can't use a value directly in setArgument:atIndex: message:
[drawInvocation setArgument: 2.0 atIndex: 3]; // WRONG!
- If the selector has an argument that is not an object (an argument that is a primitive C type such as int or float), you may use the address of the primitive type directly. There is no need to wrap the width argument in an NSNumber object.
- The arguments are identified by their position in the message. Notice that indices start at 2. Indices 0 and 1 are reserved for the hidden method arguments self and _cmd. (For an explanation of a method's hidden arguments, see Chapter 5, "Messaging.")
Now that you have created drawInvocation, you can store it or hand it off to other code. When you are ready to draw the line, you simply execute the following line of code:
[drawInvocation invoke];
An invocation like the preceding one could be used as part of a display list scheme in a drawing program. Each invocation, stored in an array, encapsulates the message required to draw an element in the final scene. When you are ready to draw the scene, you loop through the array of invocations and invoke each one in turn:
NSMutableArray *displayList = ... for NSInvocation invocation in displayList { [invocation invoke]; }
Two additional points:
- An NSInvocation can be invoked any number of times.
-
It is possible to encapsulate a message with a return value in an NSInvocation. To get the return value, send the invocation a getReturnValue: message, as illustrated here:
double result; [invocationReturningDouble getReturnValue: &result];
The argument to getReturnValue: must be a pointer to a variable of the same type as the invocation's return type. If you send a getReturnValue: message to an invocation object before it has been sent an invoke message, the result is undefined. The value is garbage and may cause a program crash if you attempt to it for anything.
NSInvocation objects are used in the Cocoa framework to schedule an operation to be performed after a time interval (NSTimer), and in the Cocoa undo mechanism (NSUndoManager). NSInvocation objects solve one of the problems of using function pointers; they carry at least some of their context with them in the form of the arguments to the encapsulated message. That said, NSInvocation objects have a major drawback—as you have seen in Listing 16.2, they are difficult to construct.