NSInvocation
Understand NSInvocation as an object representation of a method call -- its target, selector, arguments, and return value -- and how it enables dynamic invocation, deferred or queued method calls, and the message forwarding machinery, building directly on the earlier Message Forwarding lesson.
Ordinary Objective-C message sends -- [receiver doSomething:arg] -- happen and are gone; there is no object representing 'this particular call' that code can hold onto, inspect, delay, or replay later. NSInvocation exists to fill exactly that gap: it packages up a target, a selector, a full set of arguments, and eventually a return value, into a single object that can be built once and invoked whenever, wherever, and however many times is needed. This lesson works through NSInvocation piece by piece -- constructing one from a method signature, setting its target and selector, filling in arguments at the correct indexes, invoking it, and reading back its return value -- and connects it directly to the Message Forwarding lesson's forwardInvocation: method, which is, under the hood, built entirely out of exactly this object.
Why NSInvocation Exists: Calls as Objects
A normal message send is evaluated immediately: the selector, the arguments, and the receiver are all resolved and the call happens right there in the source code, with nothing left over afterward that represents 'the call' as a value you could store in a variable, pass to another method, or fire off again later. Most of the time that's exactly what you want -- but there are real situations where code needs to capture a method call itself as data: deferring it to run later, queuing several of them up to run in sequence, recording them for an undo system to replay in reverse, or handing one off to a proxy object that doesn't know in advance what selectors it will need to support.
NSInvocation is Objective-C's answer to that need: it is a full, mutable object representation of a single method call -- everything the runtime needs to actually perform that call, held as ordinary object state, until something explicitly tells it to invoke. Because it's an object, it can be stored in an array, passed around, held onto past the scope where it was built, and invoked exactly once or many times.
The clearest illustration of why this matters is NSUndoManager, which records the inverse of an action performed by the user as an NSInvocation (through methods like prepareWithInvocationTarget:) and simply invokes it later, when the user chooses Undo -- a use case that would be nearly impossible to express cleanly with ordinary, immediate message sends.
• A normal message send has no leftover representation of itself -- it happens and is gone. • NSInvocation packages a target, selector, arguments, and eventual return value into one storable, reusable object. • Real uses: deferred calls, queued calls, undo systems (NSUndoManager), and message forwarding. • Because it's an object, an NSInvocation can be built once, stored, and invoked later -- possibly more than once.
NSMethodSignature: What an NSInvocation Is Built From
An NSInvocation cannot be created out of nothing -- it needs to know, in advance, exactly how many arguments the call takes and what type each one (and the return value) is, so it can correctly lay out the underlying call frame. That information comes from an NSMethodSignature, obtained by asking an object (or a class) for the signature matching a given selector, with methodSignatureForSelector:.
The companion lesson, NSMethodSignature, covers this object in full depth -- how it exposes numberOfArguments, methodReturnType, and per-argument type encodings -- but the one fact that matters here is the dependency itself: you cannot construct an NSInvocation directly; you always go through invocationWithMethodSignature:, and that method signature is what makes the resulting NSInvocation type-aware enough to set arguments and read back a return value safely.
• NSInvocation cannot be built without a matching NSMethodSignature -- it describes argument count and every type involved. • methodSignatureForSelector: is how that signature is obtained from an object or class, for a given selector. • See the companion NSMethodSignature lesson for full coverage of what the signature itself exposes. • invocationWithMethodSignature: is the only supported way to construct an NSInvocation.
Building an Invocation: Target, Selector, and Arguments
Once a method signature is in hand, invocationWithMethodSignature: produces a fresh NSInvocation with that signature locked in. From there, target and selector are simply assigned as properties: invocation.target = object; and invocation.selector = selector; tell the invocation which object the call should ultimately be sent to and which method to call on it.
Arguments are filled in with setArgument:atIndex:, which takes a pointer to the argument value (not the value itself) and the index it belongs at in the underlying call. Indexes 0 and 1 are reserved -- exactly as with the hidden self and _cmd parameters every Objective-C method secretly receives -- so index 0 is always the target (self) and index 1 is always the selector (_cmd); the first real, user-supplied argument to the method being invoked starts at index 2.
This off-by-two indexing trips up nearly everyone the first time they use NSInvocation, precisely because it mirrors something usually invisible: every Objective-C method is, underneath, a plain C function whose first two parameters are always the receiver and the selector, and NSInvocation's argument indexing exposes that fact directly rather than hiding it.
• invocation.target sets the object the call will ultimately be sent to. • invocation.selector sets which method to invoke on that target. • setArgument:atIndex: takes a pointer to the value, not the value itself, and writes it into the invocation's argument frame. • Index 0 is always self (the target), index 1 is always _cmd (the selector) -- real arguments begin at index 2.
Invoking and Reading Back the Return Value
Once target, selector, and every argument are set, calling [invocation invoke] actually performs the message send: it behaves, from the receiving object's point of view, exactly like an ordinary message send with the given selector and arguments -- the object being invoked upon has no way to tell the difference between a call that arrived through NSInvocation and one written as ordinary [receiver doSomething:arg] syntax.
If the method has a return value, it isn't returned directly from invoke -- it's retrieved afterward with getReturnValue:, which, like setArgument:atIndex:, takes a pointer to a variable to write the result into rather than handing the value back as a return value itself. This mirrors setArgument:atIndex:'s pointer-based style deliberately: NSInvocation deals in raw, type-erased memory copies because it has to support arbitrary argument and return types, from full objects down to plain C structs and primitives, uniformly through one interface.
getReturnValue: relies on the method signature's methodReturnType to know how many bytes to copy into the buffer you provide, which is exactly why NSInvocation cannot be built or used correctly without the NSMethodSignature it started from -- the signature is what makes both setArgument:atIndex: and getReturnValue: memory-safe.
• [invocation invoke] performs the actual call -- indistinguishable, to the receiver, from an ordinary message send. • The return value is not returned from invoke -- it's retrieved afterward with getReturnValue:, which also takes a pointer to write into. • Both setArgument:atIndex: and getReturnValue: work with raw pointers because NSInvocation must support arbitrary types uniformly. • getReturnValue: relies on the method signature's methodReturnType to know exactly how many bytes to copy.
Dynamic Invocation: Calling Methods You Don't Know About Until Runtime
'Dynamic invocation' means constructing and firing off a method call whose target, selector, or arguments aren't fixed in the source code -- they're determined while the program is running, often from data (a string selector name loaded from a config file, a plugin describing what method to call, a UI element wired up to an arbitrary action). performSelector:withObject: covers the simple case of this, but it's limited to a small, fixed number of arguments and can't express arbitrary argument types or read back non-object return values.
NSInvocation is the general-purpose tool for the same underlying problem: given nothing but a selector (perhaps built at runtime with NSSelectorFromString) and a target, code can obtain the correct method signature, build an invocation, fill in however many arguments of whatever types the method actually needs, and invoke it -- all without the call ever having been written literally, with that exact selector and argument list, anywhere in the source code.
This generality is exactly what makes NSInvocation the right tool for building generic dispatch systems: a network layer that calls a different completion selector per request type, a test framework that invokes arbitrary test methods it discovers by introspection, or a scripting bridge that translates external commands into Objective-C method calls -- all of these need to invoke methods whose exact shape isn't known until the program is already running.
• Dynamic invocation means building and firing a call whose target/selector/arguments are determined at runtime, not fixed in source. • performSelector:withObject: handles the simplest cases; NSInvocation generalizes to arbitrary argument counts and types. • A selector can itself be constructed at runtime, e.g. with NSSelectorFromString, and paired with an NSInvocation to call a method never spelled out literally in source. • Common uses: generic dispatch systems, test-method discovery and invocation, scripting bridges, plugin architectures.
Forwarding: Where NSInvocation Meets the Message Forwarding Lesson
The Message Forwarding lesson covered what happens when an object is sent a selector it does not implement and ordinary dynamic method resolution has already failed: as a last resort, the runtime gives the object one more chance to handle the call by invoking forwardInvocation:, passing it -- exactly as the name says -- an NSInvocation representing the call that could not be dispatched normally.
This is the single most important connection in this lesson: forwardInvocation: does not receive a selector and a loose list of arguments the way you might expect from a simpler callback design -- it receives a fully-formed NSInvocation, already carrying the original target, selector, and every argument from the failed call, built by the runtime using exactly the mechanism this lesson has walked through by hand. A forwardInvocation: implementation typically redirects that same invocation to some other object entirely, by changing its target and calling invoke on it again -- which is only possible because the invocation, as an object, can be redirected and re-invoked freely.
Seen this way, NSInvocation is not a niche, rarely-used class -- it is the concrete object representation that the entire forwarding mechanism, and by extension features like Objective-C proxy objects and multiple-inheritance-style composition through forwarding, is built directly on top of.
• forwardInvocation:, from the Message Forwarding lesson, receives exactly an NSInvocation -- not a bare selector and argument list. • That NSInvocation already carries the original target, selector, and arguments from the call that failed ordinary dispatch. • A typical forwardInvocation: implementation retargets the invocation to a different object and calls invoke again on it. • The entire forwarding mechanism -- and features built on it, like proxy objects -- rests on exactly the NSInvocation mechanics this lesson covers.
Code Examples
This is the required starting sequence for any NSInvocation: obtain the method signature for the exact selector you intend to call, build the invocation from that signature (never directly), and then assign target and selector as ordinary properties. Nothing has actually been called yet -- the invocation is fully built but inert until invoke is sent to it.
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
setArgument:atIndex: takes a pointer to the value (&name, not name), and the index reflects the fact that every Objective-C method is secretly a C function of the form returnType method(id self, SEL _cmd, ...) -- indexes 0 and 1 are always reserved for self and _cmd respectively, whether or not you ever set them explicitly, so greetWithName:'s one real parameter lands at index 2.
[invocation setArgument:&name atIndex:2];
Common mistake: Passing name directly instead of &name, or using index 0 for the first real argument instead of index 2.
[invocation invoke] performs the actual call to -greetWithName: on object -- from Greeter's point of view, this is indistinguishable from an ordinary [object greetWithName:name] send. Because greetWithName: returns an NSString *, getReturnValue:&result copies that returned pointer into result; note that getReturnValue: is used after invoke, never before, since there is nothing to retrieve until the call has actually happened. Expected result: Hello, Ada!
[invocation getReturnValue:&result];
Nothing in this code names a fixed selector in source -- selectorName is read from configuration at runtime, converted into a real SEL with NSSelectorFromString, checked with respondsToSelector: for safety, and then invoked through NSInvocation exactly as any other selector would be. This is the general pattern behind plugin systems, config-driven dispatch, and scripting bridges: the exact method to call is genuinely unknown until the program is already running.
SEL dynamicSelector = NSSelectorFromString(selectorName);
This is the payoff of the whole lesson: forwardInvocation: is handed a fully-built NSInvocation by the runtime, carrying the exact selector and arguments the caller originally sent to Proxy but that Proxy itself doesn't implement. Because it's a genuine, mutable object, retargeting is as simple as reassigning invocation.target to realObject and invoking it again -- the same object, redirected, rather than something built from scratch.
invocation.target = self.realObject; [invocation invoke];
SEL selector = @selector(greetWithName:);
NSMethodSignature *signature =
[object methodSignatureForSelector:selector];
NSInvocation *invocation =
[NSInvocation invocationWithMethodSignature:signature];
invocation.target = object;
invocation.selector = selector;SEL selector = @selector(greetWithName:);
NSMethodSignature *signature = [object methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = object;
invocation.selector = selector;
NSString *name = @"Ada";
// Index 0 = self (the target), index 1 = _cmd (the selector),
// so the first real argument to greetWithName: goes at index 2.
[invocation setArgument:&name atIndex:2];@interface Greeter : NSObject
- (NSString *)greetWithName:(NSString *)name;
@end
@implementation Greeter
- (NSString *)greetWithName:(NSString *)name {
return [NSString stringWithFormat:@"Hello, %@!", name];
}
@end
Greeter *object = [[Greeter alloc] init];
SEL selector = @selector(greetWithName:);
NSMethodSignature *signature = [object methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = object;
invocation.selector = selector;
NSString *name = @"Ada";
[invocation setArgument:&name atIndex:2];
[invocation invoke];
NSString *result;
[invocation getReturnValue:&result];
NSLog(@"%@", result); // "Hello, Ada!"NSString *selectorName = configuredActionName(); // e.g. loaded from a plist
SEL dynamicSelector = NSSelectorFromString(selectorName);
if ([target respondsToSelector:dynamicSelector]) {
NSMethodSignature *signature =
[target methodSignatureForSelector:dynamicSelector];
NSInvocation *invocation =
[NSInvocation invocationWithMethodSignature:signature];
invocation.target = target;
invocation.selector = dynamicSelector;
[invocation invoke];
}@interface Proxy : NSObject
@property (nonatomic, strong) id realObject;
@end
@implementation Proxy
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [self.realObject methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
// The runtime built this invocation for us, already carrying
// the original selector and arguments -- we just retarget it.
invocation.target = self.realObject;
[invocation invoke];
}
@endWhere NSInvocation Shows Up in Practice
Outside of message forwarding, the most visible everyday use of NSInvocation is NSUndoManager, whose prepareWithInvocationTarget: builds an invocation representing the inverse of whatever action just happened, ready to replay later if the user chooses Undo. It also appears in older networking and delegate-dispatch code that needed to call a caller-supplied selector with an arbitrary, not-known-in-advance argument list, and in any proxy-object or dynamic-dispatch system built directly on top of forwardInvocation:.
[invocation setArgument:name atIndex:2]; // passing the value, not a pointer
[invocation setArgument:&name atIndex:2]; // passing a pointer to the value setArgument:atIndex: always takes a pointer to the argument's storage, regardless of the argument's type -- object, primitive, or struct. Passing the value directly instead of its address is a type mismatch that either fails to compile or corrupts the invocation's argument frame.
[invocation setArgument:&name atIndex:0]; // treating index 0 as the first real argument
[invocation setArgument:&name atIndex:2]; // index 0 = self, index 1 = _cmd, first real arg = 2
Indexes 0 and 1 are always reserved for the implicit self and _cmd parameters every Objective-C method secretly takes as a plain C function. The first argument you actually declared in the method signature always starts at index 2, not 0.
NSString *result = [invocation invoke]; // treating invoke as if it returns the result
[invocation invoke]; NSString *result; [invocation getReturnValue:&result];
invoke's job is only to perform the call -- it does not return the method's result directly. The return value must be retrieved afterward with getReturnValue:, which, like setArgument:atIndex:, writes into a pointer you provide rather than handing the value back as a normal return value.
What is the fundamental purpose of NSInvocation? A. It is a faster replacement for ordinary message sends in performance-critical code B. It represents a single method call -- target, selector, arguments, and eventual return value -- as a storable, reusable object C. It is a category on NSObject that adds logging to every method call automatically D. It replaces @selector() as the preferred way to reference method names
B. It represents a single method call -- target, selector, arguments, and eventual return value -- as a storable, reusable object — NSInvocation packages up everything needed to perform a method call -- target, selector, and arguments -- as a genuine object, which can be built once, stored, passed around, and invoked later, possibly more than once, unlike an ordinary immediate message send.
What must be obtained before an NSInvocation can be constructed, and why? A. Nothing -- invocationWithMethodSignature: can be called with a nil signature for simple methods B. An NSMethodSignature for the target selector, because it describes the argument count and every type involved, which the invocation needs to lay out its call frame correctly C. An NSProxy instance, because only proxy objects can build invocations D. A completion block, because NSInvocation always calls back asynchronously
B. An NSMethodSignature for the target selector, because it describes the argument count and every type involved, which the invocation needs to lay out its call frame correctly — NSInvocation is always built with invocationWithMethodSignature:, using a signature obtained from methodSignatureForSelector:. That signature is what makes setArgument:atIndex: and getReturnValue: memory-safe by describing exactly how many arguments there are and what type each one is.
Given the example code that sends [invocation setArgument:&name atIndex:2] for a method greetWithName:, why is the index 2 rather than 0? A. Index 2 is arbitrary and has no particular meaning B. Indexes 0 and 1 are reserved for the implicit self and _cmd parameters every Objective-C method secretly takes, so the first real argument starts at index 2 C. NSInvocation always numbers arguments starting from 2 for historical reasons unrelated to self and _cmd D. Index 2 refers to the second argument; greetWithName: must therefore take at least two parameters
B. Indexes 0 and 1 are reserved for the implicit self and _cmd parameters every Objective-C method secretly takes, so the first real argument starts at index 2 — Every Objective-C method is, underneath, a plain C function whose first two parameters are always (id self, SEL _cmd, ...). NSInvocation's argument indexing exposes that directly: index 0 is self, index 1 is _cmd, and the first genuinely user-supplied argument begins at index 2.
How is a method's return value obtained from an NSInvocation after calling invoke? A. invoke itself returns the value directly, like an ordinary message send B. The return value is automatically logged to the console; it cannot be captured programmatically C. By calling getReturnValue:, passing a pointer to a variable for the invocation to copy the result into D. By reading invocation.selector, which is repurposed to hold the return value after invoke completes
C. By calling getReturnValue:, passing a pointer to a variable for the invocation to copy the result into — invoke only performs the call. The return value is retrieved afterward with getReturnValue:, which -- like setArgument:atIndex: -- takes a pointer to write into rather than handing the value back as an ordinary return value, because NSInvocation must support arbitrary return types uniformly.
How does NSInvocation relate to the forwardInvocation: method from the Message Forwarding lesson? A. They are unrelated; forwardInvocation: receives a bare selector and argument array, not an NSInvocation B. forwardInvocation: creates a brand-new NSInvocation from scratch every time it is called C. forwardInvocation: is handed a fully-built NSInvocation by the runtime, already carrying the original target, selector, and arguments from the call that failed ordinary dispatch -- exactly the mechanism this lesson covers D. NSInvocation replaces forwardInvocation: entirely in modern Objective-C
C. forwardInvocation: is handed a fully-built NSInvocation by the runtime, already carrying the original target, selector, and arguments from the call that failed ordinary dispatch -- exactly the mechanism this lesson covers — The runtime constructs an NSInvocation representing the failed call and passes it to forwardInvocation:, which typically retargets that same invocation to another object and calls invoke on it again. The entire forwarding mechanism is built directly on top of the NSInvocation mechanics this lesson walks through by hand.
Why is NSInvocation, rather than performSelector:withObject:, the right tool for a generic dispatch system that must call methods with an arbitrary number and type of arguments determined at runtime? A. performSelector:withObject: is deprecated and no longer works on any platform B. NSInvocation is faster than performSelector:withObject: in every case C. performSelector:withObject: is limited to a small, fixed number of object arguments and can't express arbitrary argument types or non-object return values, while NSInvocation handles arbitrary argument counts and types uniformly through its method signature D. NSInvocation does not require knowing the selector in advance, while performSelector:withObject: does
C. performSelector:withObject: is limited to a small, fixed number of object arguments and can't express arbitrary argument types or non-object return values, while NSInvocation handles arbitrary argument counts and types uniformly through its method signature — performSelector:withObject: (and its variants) only support a small fixed number of object-typed arguments. NSInvocation generalizes fully: any number of arguments, any type (including primitives and structs), and any return type, all described precisely by the NSMethodSignature it's built from.
Write a Calculator class with an instance method - (NSInteger)addNumber:(NSInteger)a toNumber:(NSInteger)b that returns a + b. Then write standalone code that builds an NSInvocation to call addNumber:toNumber: with the values 4 and 9, invokes it, and logs the retrieved return value.
@interface Calculator : NSObject\n- (NSInteger)addNumber:(NSInteger)a toNumber:(NSInteger)b;\n@end\n\n@implementation Calculator\n- (NSInteger)addNumber:(NSInteger)a toNumber:(NSInteger)b {\n return a + b;\n}\n@end\n\nCalculator *calc = [[Calculator alloc] init];\nSEL selector = @selector(addNumber:toNumber:);\nNSMethodSignature *signature = [calc methodSignatureForSelector:selector];\nNSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];\ninvocation.target = calc;\ninvocation.selector = selector;\n\nNSInteger a = 4;\nNSInteger b = 9;\n[invocation setArgument:&a atIndex:2];\n[invocation setArgument:&b atIndex:3];\n\n[invocation invoke];\n\nNSInteger result;\n[invocation getReturnValue:&result];\nNSLog(@"%ld", (long)result); // 13
Remember the argument indexing: index 2 is the first parameter (a), index 3 is the second parameter (b). NSInteger is a primitive, not an object, so getReturnValue: still takes a pointer to an NSInteger variable, exactly the same way it would for an object return type.