NSMethodSignature

Understand NSMethodSignature as the object that describes a method's shape -- its return type, argument count, and per-argument types via Objective-C type encodings -- and its role as the foundation NSInvocation is built from and the linchpin of the message forwarding mechanism.

The previous lesson, NSInvocation, showed that an invocation cannot be constructed out of nothing: it needs a companion object that already knows exactly how many arguments a method takes and what type each of them (and the return value) is. That companion is NSMethodSignature. This lesson is the sibling to NSInvocation, focused specifically on the signature object itself -- how to obtain one with methodSignatureForSelector:, what it exposes (numberOfArguments, methodReturnType, per-argument type encodings), why self and _cmd occupy fixed argument slots in every signature, and why supplying a valid method signature is the first, non-negotiable step of Objective-C's message forwarding mechanism.

What an NSMethodSignature Actually Describes

An NSMethodSignature is an object that fully describes the shape of a method call, independent of any specific invocation of it: how many arguments it takes, what C type each argument is, and what C type its return value is. It does not describe what the method does, and it is not tied to any one target -- it describes the calling convention, the way a blueprint describes a building's structure without being a specific, built instance of that building.

This is exactly the information an NSInvocation needs before it can safely accept arguments or hand back a return value: without knowing that a given method's second real argument is an NSInteger and its return type is a BOOL, there would be no way to know how many bytes to copy into or out of the invocation's internal call frame. NSMethodSignature is what supplies that missing information.

The diagram accompanying this lesson lays out the relationship precisely: starting from a selector, NSMethodSignature breaks the call down into its return type, its self slot, its _cmd slot, and its actual arguments -- the full shape of the underlying C function every Objective-C method really is.

• NSMethodSignature describes a method's calling shape -- argument count, argument types, return type -- not what the method does. • It is not tied to any specific target object; it describes the calling convention shared by every object that responds to a given selector the same way. • It supplies exactly the information NSInvocation needs to safely accept arguments and hand back a return value. • It breaks a call down into: return type, self, _cmd, and the method's own arguments -- shown in this lesson's diagram.

Obtaining a Signature: methodSignatureForSelector:

methodSignatureForSelector: is the standard way to obtain an NSMethodSignature: send it to an object (or a class, for class methods), passing the SEL you're interested in, and it returns the signature describing how that selector is actually implemented for that object's class -- or nil if the object genuinely cannot resolve any implementation for that selector at all, including through forwarding.

Every NSObject subclass inherits a working implementation of methodSignatureForSelector: for granted, ordinary methods -- you rarely need to think about it explicitly in everyday code. It becomes something you write yourself, rather than merely call, specifically when building a proxy or forwarding object, which the last section of this lesson covers in detail.

It's worth noting the parallel with NSInvocation's own construction: just as invocationWithMethodSignature: is the only supported way to build an invocation, methodSignatureForSelector: is the standard, supported way to obtain the signature that construction depends on -- there is no public API for hand-assembling an NSMethodSignature from scratch out of individual type strings.

• methodSignatureForSelector: is sent to an object or class with a SEL, returning the NSMethodSignature describing that selector's implementation. • Returns nil if the target genuinely cannot resolve any implementation for that selector, including through forwarding. • Ordinary classes inherit a working implementation for free; proxy/forwarding classes often need to override it explicitly. • There is no public API to hand-build an NSMethodSignature directly -- it is always obtained via methodSignatureForSelector:.

Reading a Signature: numberOfArguments, methodReturnType, getArgumentTypeAtIndex:

Once obtained, a signature exposes its shape through a small set of properties and methods. numberOfArguments returns the total argument count -- and, critically, this count always includes the implicit self and _cmd parameters, so a method declared with one user-facing parameter reports numberOfArguments as 3, not 1.

methodReturnType returns a C string containing the Objective-C type encoding for the method's return value -- a single character or short sequence like "@" for an object, "i" for int, "B" for BOOL, "v" for void, and so on. getArgumentTypeAtIndex: returns the same kind of type-encoding string, but for one specific argument slot, addressed by the same index scheme NSInvocation's setArgument:atIndex: uses.

These type-encoding strings are not NSMethodSignature's own invention -- they are the same Objective-C runtime type encodings the compiler generates for every method and property throughout the language, the exact subject the upcoming Objective-C Type Encodings addendum lesson covers in full depth. NSMethodSignature is, in effect, a convenient object wrapper around parsing and using those encoding strings without having to hand-parse the raw C string yourself.

• numberOfArguments includes the implicit self and _cmd slots -- a one-parameter method reports 3, not 1. • methodReturnType returns the return value's Objective-C type encoding as a C string (e.g. "@" for object, "i" for int, "v" for void). • getArgumentTypeAtIndex: returns the type encoding for one specific argument slot, addressed with the same indexing NSInvocation uses. • These encodings are the same runtime type encodings covered in depth in the upcoming Objective-C Type Encodings lesson -- NSMethodSignature wraps them in a convenient object API.

Why self and _cmd Occupy Fixed Argument Slots

Every Objective-C method, no matter how it's written or called, compiles down to an ordinary C function whose first two parameters are always the receiving object and the selector being sent -- conventionally written as (id self, SEL _cmd, ...). This is not a special case or an optimization; it is simply what an Objective-C method actually is at the machine level, and it is the same underlying fact the NSInvocation lesson relied on to explain why real arguments start at index 2 rather than index 0.

NSMethodSignature reflects this reality directly and honestly rather than hiding it: because self and _cmd are genuinely part of every method's C-level parameter list, they are counted as arguments 0 and 1 in numberOfArguments and are addressable with getArgumentTypeAtIndex: exactly like any other argument -- calling getArgumentTypeAtIndex:0 on virtually any instance method's signature returns the object type encoding "@" (for self), and index 1 returns the SEL type encoding ":" (for _cmd).

Seeing this laid out in NSMethodSignature's numbering is often the moment this fact really lands for developers who have only ever seen self and _cmd as implicit, invisible values inside a method body -- the signature makes visible exactly what the compiler was doing all along.

• Every Objective-C method is secretly a C function of the form returnType method(id self, SEL _cmd, ...). • self and _cmd are counted as real argument slots: index 0 and index 1 respectively, in every method signature. • getArgumentTypeAtIndex:0 returns "@" (object) for self; getArgumentTypeAtIndex:1 returns ":" (SEL) for _cmd, for essentially any instance method. • This is exactly why NSInvocation's real, user-supplied arguments always begin at index 2, as the companion NSInvocation lesson covers.

NSMethodSignature's Role in Message Forwarding

The Message Forwarding lesson covered forwardInvocation: as the last stage of the forwarding mechanism, receiving a fully-built NSInvocation to redirect. This lesson's job is to name the step that happens immediately before that, and without which forwardInvocation: could never be reached at all: before the runtime can build an NSInvocation representing a call that failed ordinary dispatch, it must first obtain a valid NSMethodSignature for that call, by sending methodSignatureForSelector: to the object.

If methodSignatureForSelector: returns nil -- meaning the object has no way to describe the shape of that call -- the runtime has no information from which to construct an NSInvocation at all, forwardInvocation: is never even reached, and the program instead crashes with the familiar 'unrecognized selector sent to instance' error, doesNotRecognizeSelector: is invoked, and forwarding fails outright.

This is why any class that intends to forward unknown selectors -- a proxy object, a dynamic subclass, anything relying on forwardInvocation: to handle selectors it doesn't implement directly -- must override methodSignatureForSelector: to supply a valid signature for those selectors too, typically by delegating to whatever real object it's forwarding on behalf of, exactly as shown in the Proxy example in the companion NSInvocation lesson. Supplying that signature is the precondition; forwardInvocation: is the payoff that only becomes reachable once the signature is there.

• Before forwardInvocation: can be reached, the runtime must first get a valid signature via methodSignatureForSelector: for the failed selector. • A nil signature means the runtime cannot build an NSInvocation at all -- forwarding fails immediately, and 'unrecognized selector' results. • Any forwarding object must override methodSignatureForSelector: for the selectors it intends to forward, usually by delegating to the real object. • methodSignatureForSelector: is the precondition for forwarding; forwardInvocation:, from the companion NSInvocation lesson, is what actually happens once that precondition is met.

Code Examples

greetWithName: has exactly one user-facing parameter, but numberOfArguments reports 3 -- self at index 0, _cmd at index 1, and name at index 2. methodReturnType reports "@" because greetWithName: returns an NSString *, an object type, which the Objective-C type-encoding scheme represents with the single character @. Expected result: numberOfArguments: 3 methodReturnType: @

NSUInteger argCount = signature.numberOfArguments; // includes self and _cmd

Iterating every index from 0 up to numberOfArguments makes the hidden self/_cmd slots visible directly: index 0's type encoding "@" is self (an object), index 1's encoding ":" is _cmd (a SEL), and only index 2 is the argument actually written in the method's declared signature -- name. Expected result: argument 0: @ argument 1:: argument 2: @ const char *type = [signature getArgumentTypeAtIndex:i];

isValidAge:forCategory: has two user-facing parameters, so numberOfArguments is 4 (self, _cmd, age, category). The return type encoding "B" denotes BOOL, and both primitive NSInteger parameters report as "q" (the encoding for a 64-bit signed integer on 64-bit platforms) at indexes 2 and 3 -- exactly the two real argument slots NSInvocation's setArgument:atIndex: would target for this method.

NSLog(@"return type: %s", signature.methodReturnType); // "B"

This is the missing piece that makes the companion NSInvocation lesson's Proxy example actually work: before forwardInvocation: can ever run, methodSignatureForSelector: must return a real, valid signature for the selector being forwarded, obtained here by asking realObject for its own signature. Without overriding this method, Proxy would report nil for any selector it doesn't itself implement, the runtime would be unable to build an NSInvocation at all, and forwardInvocation: would never be called.

NSMethodSignature *signature = [self.realObject methodSignatureForSelector:selector];

@interface Greeter : NSObject
- (NSString *)greetWithName:(NSString *)name;
@end

Greeter *object = [[Greeter alloc] init];
SEL selector = @selector(greetWithName:);

NSMethodSignature *signature = [object methodSignatureForSelector:selector];

NSUInteger argCount = signature.numberOfArguments;
const char *returnType = signature.methodReturnType;

NSLog(@"numberOfArguments: %lu", (unsigned long)argCount);   // 3
NSLog(@"methodReturnType: %s", returnType);                   // "@"
SEL selector = @selector(greetWithName:);
NSMethodSignature *signature = [object methodSignatureForSelector:selector];

for (NSUInteger i = 0; i < signature.numberOfArguments; i++) {
    const char *type = [signature getArgumentTypeAtIndex:i];
    NSLog(@"argument %lu: %s", (unsigned long)i, type);
}
// argument 0: @   (self)
// argument 1: :   (_cmd, SEL)
// argument 2: @   (name, NSString *)
@interface Validator : NSObject
- (BOOL)isValidAge:(NSInteger)age forCategory:(NSInteger)category;
@end

SEL selector = @selector(isValidAge:forCategory:);
NSMethodSignature *signature = [validator methodSignatureForSelector:selector];

NSLog(@"numberOfArguments: %lu", (unsigned long)signature.numberOfArguments); // 4
NSLog(@"return type: %s", signature.methodReturnType);                        // "B"
NSLog(@"arg 2 type: %s", [signature getArgumentTypeAtIndex:2]);               // "q" (NSInteger)
NSLog(@"arg 3 type: %s", [signature getArgumentTypeAtIndex:3]);               // "q"
@interface Proxy : NSObject
@property (nonatomic, strong) id realObject;
@end

@implementation Proxy

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    NSMethodSignature *signature =
        [self.realObject methodSignatureForSelector:selector];
    if (signature == nil) {
        // realObject can't describe this selector either -- forwarding
        // cannot proceed, and forwardInvocation: will never be reached.
        return [super methodSignatureForSelector:selector];
    }
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    invocation.target = self.realObject;
    [invocation invoke];
}

@end

What an NSMethodSignature Breaks a Call Down Into

Starting from a selector, methodSignatureForSelector: returns an NSMethodSignature describing the return type and every parameter slot -- including the implicit self and _cmd every Objective-C method secretly takes -- which is exactly the information NSInvocation needs to be built.

Selector
   ↓
NSMethodSignature
   ├── Return Type
   ├── Self
   ├── _cmd
   └── Arguments

Where NSMethodSignature Shows Up in Practice

Outside of forwarding proxies, NSMethodSignature is most often encountered indirectly, as the required first step before building any NSInvocation -- test frameworks that invoke discovered test methods, mocking libraries that build fake implementations of arbitrary protocols, and any dynamic dispatch system that needs to know a method's exact shape before calling it all route through methodSignatureForSelector: as their starting point.

// A proxy class implementing forwardInvocation: but never overriding // methodSignatureForSelector: for the selectors it forwards @implementation Proxy - (void)forwardInvocation:(NSInvocation *)invocation { invocation.target = self.realObject; [invocation invoke]; } @end

@implementation Proxy - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { return [self.realObject methodSignatureForSelector:selector] ?: [super methodSignatureForSelector:selector]; } - (void)forwardInvocation:(NSInvocation *)invocation { invocation.target = self.realObject; [invocation invoke]; } @end forwardInvocation: is only ever reached after the runtime successfully obtains a signature via methodSignatureForSelector:. Without overriding it, Proxy reports nil for any selector it doesn't itself implement, the runtime cannot build an NSInvocation, and the call crashes with 'unrecognized selector' before forwardInvocation: is ever called.

// Assuming a one-parameter method's numberOfArguments is 1 if (signature.numberOfArguments == 1) { /* ... */ }

// A method with one user-facing parameter reports 3: // self (0), _cmd (1), and the parameter itself (2) if (signature.numberOfArguments == 3) { /*... */ } numberOfArguments always includes the implicit self and _cmd slots every Objective-C method secretly takes. Code that assumes it equals only the count of parameters written in the method's declaration will be off by exactly two, and any loop over argument indexes needs to account for that offset.

What does an NSMethodSignature describe? A. The actual implementation code of a method B. A method's calling shape -- its return type, argument count, and each argument's type -- independent of any specific invocation C. The class hierarchy a method's receiver belongs to D. A human-readable description of what a method does, for documentation purposes

B. A method's calling shape -- its return type, argument count, and each argument's type -- independent of any specific invocation — NSMethodSignature describes structure, not behavior or documentation: how many arguments a method takes, what C type each one is, and what type the return value is -- exactly the information needed to safely build and use an NSInvocation for that method.

How is an NSMethodSignature normally obtained? A. By hand-assembling one from individual Objective-C type-encoding strings B. By sending methodSignatureForSelector: to an object or class with the selector of interest C. By calling [NSMethodSignature new] D. It is generated automatically the first time NSInvocation's invoke is called

B. By sending methodSignatureForSelector: to an object or class with the selector of interest — methodSignatureForSelector:, sent to an object or class with a SEL, is the standard and only supported way to obtain the signature describing how that selector is implemented (or nil if it cannot be resolved at all, including through forwarding).

For a method declared with exactly one user-facing parameter, what does numberOfArguments report, and why? A. 1, matching the single declared parameter B. 0, because numberOfArguments only counts optional parameters C. 3, because self and _cmd are counted as argument slots 0 and 1, with the declared parameter at slot 2 D. It varies randomly depending on the method's return type

C. 3, because self and _cmd are counted as argument slots 0 and 1, with the declared parameter at slot 2 — Because every Objective-C method is secretly a C function taking (id self, SEL _cmd, ...), self and _cmd genuinely occupy the first two argument slots in the underlying calling convention, and NSMethodSignature reflects that honestly -- numberOfArguments always includes them.

What happens if methodSignatureForSelector: returns nil for a selector an object does not implement, in the context of message forwarding? A. forwardInvocation: is still called, just with a nil invocation argument B. The runtime cannot build an NSInvocation for that call at all, so forwardInvocation: is never reached, and the program crashes with 'unrecognized selector sent to instance' C. The runtime silently ignores the call and continues without error D. NSMethodSignature automatically falls back to treating every argument as an object type

B. The runtime cannot build an NSInvocation for that call at all, so forwardInvocation: is never reached, and the program crashes with 'unrecognized selector sent to instance' — A valid method signature is the precondition for building the NSInvocation that forwardInvocation: receives. Without one, the runtime has no information to construct that invocation from, forwarding fails immediately, and doesNotRecognizeSelector: takes over, producing the familiar crash.

How does NSMethodSignature relate to NSInvocation, as covered in the companion lesson? A. They are unrelated classes that happen to be used in similar contexts B. NSInvocation is what NSMethodSignature is built from C. NSMethodSignature is what NSInvocation is built from -- invocationWithMethodSignature: requires one, and it is what makes setArgument:atIndex: and getReturnValue: memory-safe D. NSMethodSignature replaces NSInvocation entirely in modern Objective-C

C. NSMethodSignature is what NSInvocation is built from -- invocationWithMethodSignature: requires one, and it is what makes setArgument:atIndex: and getReturnValue: memory-safe — An NSInvocation cannot be constructed without a matching NSMethodSignature -- invocationWithMethodSignature: is the only supported constructor, and the signature is what tells the resulting invocation how many bytes each argument and the return value occupy.

What are NSMethodSignature's per-argument and return type strings (like "@", "i", "B", "d") actually derived from? A. NSMethodSignature invents its own private encoding scheme unrelated to anything else in Objective-C B. They are the same Objective-C runtime type encodings generated by the compiler for every method and property, covered in full depth in the upcoming Objective-C Type Encodings lesson C. They are plain English abbreviations chosen for readability D. They correspond directly to Swift type names for interoperability

B. They are the same Objective-C runtime type encodings generated by the compiler for every method and property, covered in full depth in the upcoming Objective-C Type Encodings lesson — NSMethodSignature wraps the same type-encoding strings the compiler already generates throughout the Objective-C runtime -- the exact subject the upcoming Objective-C Type Encodings addendum lesson covers in depth -- rather than defining any encoding scheme of its own.

For a method declared - (double)areaOfRectangleWithWidth:(double)width height:(double)height, write code that obtains its NSMethodSignature, logs numberOfArguments, methodReturnType, and the type encoding at every argument index, and add a comment identifying which index corresponds to self, which to _cmd, which to width, and which to height.

SEL selector = @selector(areaOfRectangleWithWidth:height:);\nNSMethodSignature *signature = [calculator methodSignatureForSelector:selector];\n\nNSLog(@"numberOfArguments: %lu", (unsigned long)signature.numberOfArguments); // 4\nNSLog(@"methodReturnType: %s", signature.methodReturnType); // \"d\" (double)\n\nfor (NSUInteger i = 0; i < signature.numberOfArguments; i++) {\n NSLog(@"index %lu: %s", (unsigned long)i, [signature getArgumentTypeAtIndex:i]);\n}\n// index 0: @ -> self\n// index 1: : -> _cmd\n// index 2: d -> width\n// index 3: d -> height

There are two user-facing parameters, so numberOfArguments will be 4. double's Objective-C type encoding is "d". Index 0 is always self, index 1 is always _cmd for an instance method's signature.