objc_msgSend in Depth

Trace, in full detail, exactly what happens when an Objective-C message is sent: how [object doSomething] compiles down to a call to the C function objc_msgSend, how the runtime resolves receiver and selector into an IMP through the method cache and, on a miss, the method-list/superclass-chain walk, why calling objc_msgSend directly requires an exact function-pointer cast and is fragile because of ABI variants like objc_msgSend_fpret and objc_msgSend_stret, and why Objective-C's per-class inline caching keeps even polymorphic call sites fast.

The Objective-C Runtime lesson established, at a conceptual level, that Objective-C uses dynamic dispatch: a message sent to an object is resolved to an implementation at runtime rather than bound at compile time the way a plain C function call is. This lesson goes underneath that statement and follows the actual mechanism, function call by function call, cache lookup by cache lookup, all the way down to a raw C function pointer being jumped to. It is one of the deepest lessons in this course, on the same level as the earlier Objective-C Runtime and Blocks lessons, because objc_msgSend genuinely is the single busiest function in any running Objective-C program -- understanding it precisely is what makes Dynamic Method Resolution, Message Forwarding, and the sibling Objective-C Type Encodings lesson click together as one coherent system rather than three separate topics. The companion lesson objc_msgSendSuper picks up immediately where this one leaves off, covering the one major variation on this dispatch path that [super ...] introduces.

Every Message Send Compiles Down to a Function Call

[object doSomething] is pure syntax -- square brackets and a method name are not, themselves, any kind of instruction the processor can execute. The compiler translates that syntax into an ordinary (if unusually typed) C function call: conceptually, objc_msgSend(object, @selector(doSomething)). A message with arguments follows the identical pattern, with the arguments simply tacked on after the selector: [obj addObject:x] compiles down to objc_msgSend(obj, @selector(addObject:), x).

objc_msgSend is not a compiler intrinsic or a piece of inlined magic -- it is a real, ordinary function, written in hand-tuned assembly for performance, exported by the Objective-C runtime library and linked into every Objective-C binary. Every single message send in compiled Objective-C code -- with the one major exception this lesson's companion, objc_msgSendSuper, covers, plus a couple of narrower ABI-driven variants covered later in this lesson -- goes through this exact function.

This is the literal mechanism the earlier Objective-C Runtime lesson referred to abstractly as 'dynamic dispatch.' Where a plain C function call is resolved permanently at compile time (the address to jump to is baked into the binary), a message send resolves its destination address freshly, by looking it up, every single time objc_msgSend runs -- which is exactly why the caching machinery covered later in this lesson matters so much for performance.

• [object doSomething] compiles down to a call to the C function objc_msgSend(object, @selector(doSomething)). • Arguments are simply appended: [obj addObject:x] becomes objc_msgSend(obj, @selector(addObject:), x). • objc_msgSend is a real, hand-tuned function in the runtime library -- not a compiler intrinsic -- and nearly every message send in a compiled binary goes through it. • This is the concrete mechanism behind 'dynamic dispatch': the destination is looked up fresh on every send, not baked in at compile time.

The Dispatch Path, Stage by Stage

The diagram accompanying this lesson lays out the complete path a message send follows, from source syntax down to actual code running, as a single chain: source syntax becomes a call to objc_msgSend, which examines its receiver and selector, consults the receiver's class, checks that class's method cache, performs a full method lookup on a cache miss, arrives at an IMP, and finally reaches the implementation itself.

Every stage in that chain corresponds to a real, distinct piece of work the runtime performs, and the rest of this lesson works through each one individually: what receiver and selector actually are as raw values, how the method cache makes repeated sends fast, exactly what happens on a cache miss, what an IMP actually is once you have one, and why the very last step -- jumping to the implementation -- is close to as cheap as an ordinary C function call once everything above it has resolved.

• The path is sequential: syntax -> objc_msgSend -> receiver + selector -> receiver's class -> method cache -> (on a miss) full method lookup -> IMP -> implementation. • Each stage is real, distinct work performed by the runtime -- not a conceptual simplification of a single opaque step. • The final jump to the implementation, once an IMP is in hand, is close in cost to an ordinary C function call.

Receiver and Selector: The Two Required Inputs

Every call to objc_msgSend takes, as its first two arguments, exactly the two pieces of information required to resolve any message: the receiver (the object the message is being sent to, typed id) and the selector (a SEL value identifying the method name being invoked, obtained via @selector(...) or at runtime via NSSelectorFromString). Any further arguments the actual message carries are simply appended after those two.

This lines up exactly with the sibling Objective-C Type Encodings lesson's observation that every method secretly takes self and _cmd as its first two hidden parameters: those two hidden parameters are precisely the receiver and selector objc_msgSend itself received, passed straight through to whatever implementation is ultimately reached. The receiver and selector aren't consumed or transformed by dispatch -- they're threaded all the way through it, unchanged, and handed to the final implementation exactly as they arrived.

• Receiver: the object (id) the message is sent to -- objc_msgSend's first argument, always. • Selector: a SEL value naming the method -- objc_msgSend's second argument, always. • These are exactly the self and _cmd hidden parameters described in the Objective-C Type Encodings lesson -- passed straight through, unchanged, to whatever implementation dispatch eventually reaches.

Method Cache: The Fast Path

Every class maintains its own method cache: a small, hash-table-like structure mapping selectors to the IMPs the runtime has already resolved for that class. When objc_msgSend runs, after identifying the receiver's class, the very first thing it does is check that class's cache for the incoming selector.

A cache hit is the fast path, and it is genuinely fast -- close to the cost of a hash lookup and a pointer comparison, nothing more. No method list is walked, no superclass is consulted, nothing resembling a search happens at all; the cache simply already has the answer from a previous send of the same selector to an instance of the same class. This is exactly why repeated messaging of the same selector to instances of the same class, which is the overwhelmingly common pattern in real programs (calling a getter in a loop, sending the same delegate callback repeatedly), stays cheap despite being resolved dynamically on every single call.

• Every class maintains its own method cache, mapping SEL -> IMP for selectors already resolved for that class. • objc_msgSend checks the receiver's class's cache first, before anything else. • A cache hit is close to a hash lookup plus a comparison -- no method-list or superclass walk at all. • This is why repeated sends of the same selector to the same class stay cheap, even though resolution happens fresh, in principle, on every call.

Cache Miss: Method List and Superclass Chain Walk (The Slow Path)

On a cache miss -- the very first time a given selector is sent to instances of a particular class, or after certain runtime changes invalidate a cache -- objc_msgSend falls back to the slow path: it walks the receiver's actual class's own method list, comparing selectors one by one, looking for a match.

If no match is found in the class's own method list, the runtime moves up to that class's superclass and repeats the same search, then that superclass's superclass, and so on, all the way up the inheritance chain toward the root class (typically NSObject). This is method resolution honoring inheritance directly: a subclass that doesn't override a given method simply doesn't have it in its own method list, so the walk naturally finds the inherited implementation higher up the chain.

If the entire superclass chain is exhausted with no match found anywhere, the runtime does not simply fail silently -- it falls into the dynamic method resolution and message forwarding machinery covered in the sibling Dynamic Method Resolution and Message Forwarding lessons, giving the class one last set of opportunities to supply an implementation before the program crashes with 'unrecognized selector.' Once a match is found anywhere along this walk, the resulting IMP is written into the original receiver's class's cache -- not necessarily the class where the method was actually found -- so that every future send of that exact selector to that exact class hits the fast cache path from then on.

• On a miss, the runtime walks the receiver's own class's method list first, comparing selectors one by one. • If not found there, it repeats the search up the superclass chain, toward the root class -- this is how inherited methods are actually located. • Exhausting the whole chain with no match triggers dynamic method resolution and message forwarding, covered in their own sibling lessons, before an unrecognized-selector crash. • Once found anywhere in the chain, the resulting IMP is cached on the ORIGINAL receiving class, so every future send for that class hits the fast path.

IMP: The Raw Function Pointer at the End of Lookup

Whether reached via a cache hit or a full slow-path walk, the end result of method lookup is always the same kind of value: an IMP, a raw C function pointer to the actual block of executable code that implements the method for whatever class it was found on. This is precisely the IMP described in the sibling Objective-C Type Encodings and Advanced Type System material -- the 'code half' of the SEL/IMP pair, as opposed to SEL, which is only ever a name.

Once objc_msgSend has an IMP in hand, the very last step is close to the simplest part of the whole process: it jumps to (in practice, tail-calls) that function pointer, passing along the exact same receiver, selector, and any additional arguments it originally received. There is no interpreter involved, no further indirection beyond the single cache-or-lookup step already performed, and no boxing or unboxing of arguments -- once resolved, invoking the implementation costs barely more than an ordinary C function call, which is exactly why Objective-C's dynamism doesn't carry anywhere near the performance penalty a naive 'look everything up every time, the slow way' implementation would.

• IMP: a raw C function pointer to a method's actual implementation code -- what lookup ultimately produces, whether from a cache hit or a slow-path walk. • objc_msgSend's final step is a near-direct jump to that IMP, passing the same receiver, selector, and arguments through unchanged. • No interpreter, no further indirection, no argument boxing -- the final call is close in cost to an ordinary C function call.

Calling objc_msgSend Directly: Casting and Why It's Fragile

objc_msgSend's own declared signature is deliberately generic and, on its face, almost unusable directly: something like id objc_msgSend(id self, SEL op, ...). That signature is a placeholder the compiler uses to generate the correct call for you automatically behind ordinary message-send syntax -- it is not a signature you can safely call through as-is for anything beyond the most trivial, simple-argument, id-returning case.

To call objc_msgSend directly and correctly for a specific method, you must first cast the function pointer to the exact C function type that matches that method's real return type and argument types -- for example, ((void (*)(id, SEL))objc_msgSend)(obj, sel) for a method that returns void and takes no extra arguments. Get any part of that cast wrong -- the wrong return type, the wrong argument types, or the wrong argument count -- and the underlying call reads or writes the wrong registers or stack slots, corrupting the call in exactly the same way a wrong type-encoding string corrupts an NSInvocation, as the sibling Objective-C Type Encodings lesson covers from the other direction.

This fragility gets worse, not better, once floating-point returns and struct returns enter the picture, because different calling conventions on different processor architectures and Apple platforms have historically handled these cases through entirely separate entry points rather than through plain objc_msgSend. objc_msgSend_fpret was historically used on some platforms for methods returning float or double, because the calling convention for returning a floating-point value uses different registers than returning an ordinary object pointer. objc_msgSend_stret is used when a method's return value is a struct too large to fit in the registers an ordinary return uses -- on such calling conventions, the caller instead allocates space for the result and passes a hidden pointer to that space as an extra, implicit first argument, which is a fundamentally different call shape than a plain id-returning send. Modern ARM64-based Apple platforms unify much of this handling compared to older i386/x86_64 conventions, but the underlying principle never goes away: the function-pointer type used to call objc_msgSend (or whichever variant applies) must exactly match the real method's actual return type and argument types, or the call corrupts the stack or registers.

Because getting any of this wrong is a silent, ABI-level bug rather than something the compiler can catch for you, virtually all production code should never call objc_msgSend directly. Ordinary message-send syntax exists precisely so the compiler selects the correct entry point and generates the correct cast for you automatically, every time. Direct calls to objc_msgSend belong to very low-level runtime tooling -- debuggers, profilers, and the runtime's own internals -- not application code.

• objc_msgSend's own declared signature is a generic placeholder -- calling it correctly requires casting the function pointer to the method's exact real return and argument types first. • A wrong cast (return type, argument types, or argument count) corrupts the call at the register/stack level -- the same failure mode a wrong type-encoding string produces for NSInvocation. • objc_msgSend_fpret existed historically for floating-point-returning methods; objc_msgSend_stret is used for large struct returns, which pass a hidden pointer to caller-allocated storage as an extra implicit argument. • Ordinary [obj message] syntax lets the compiler choose the correct entry point and generate the correct cast automatically -- direct objc_msgSend calls belong to low-level tooling, not application code.

Performance: Inline Caching vs. Naive Vtable Dispatch

Because each class maintains its own independent method cache, repeated messaging of the same selector to instances of the same class benefits from what is effectively inline caching: after the very first send resolves a SEL to an IMP for that class, every subsequent send of that same selector to any instance of that class hits the fast cache path described earlier -- comparable in cost to a couple of memory loads and a comparison, nowhere near a full method-list or superclass walk.

Polymorphic call sites -- the same source-level [obj doSomething] appearing once in code, but invoked across a mix of different classes over the life of a program, such as inside a loop over a heterogeneous array -- remain fast too, and for a specific, structural reason: each class maintains its OWN cache, entirely independent of every other class's cache. A loop sending the same selector to a mix of, say, Circle and Square instances gets a fast cache hit for Circle's own cache and a separate fast cache hit for Square's own cache, rather than sharing one global lookup table that would need to distinguish between them on every call.

This per-(class, selector) caching model is genuinely different from -- and, for the common case of a call site that only ever sees a handful of distinct classes, often faster in practice than -- a naive single global vtable-index scheme used by some other object systems, where a fixed table slot is indexed the same way on every call regardless of which classes are actually seen at that call site. Objective-C's dispatch pays a real cost the first time any given (class, selector) pair is seen, but essentially amortizes that cost to nothing on every subsequent send, which is exactly why 'dynamic' dispatch in Objective-C does not, in practice, mean 'slow.'

• Each class's own cache functions as inline caching: after the first resolution, every later send of that selector to that class hits a fast, near-constant-time path. • Polymorphic call sites stay fast because each class involved maintains its OWN independent cache -- no shared table that needs to distinguish between classes on every call. • This differs from a naive single global vtable-index scheme, and is often faster in practice for call sites that see only a small, stable set of classes. • The real cost is paid once, on the first send of a given (class, selector) pair, and is amortized to near zero on every subsequent send.

Code Examples

Ordinary bracket syntax hides all of this -- three separate message sends here (alloc, init, and the two later calls) each become their own objc_msgSend call, with the compiler choosing the correct cast for each one based on the method's actual declared signature. This is exactly why calling objc_msgSend by hand, shown in the next example, requires supplying that same cast yourself.

[counter increment]; // compiles to a cast objc_msgSend call, not a direct jump

The cast id (*)(id, SEL) matches -description's real signature exactly -- an object return, receiver, and selector, with no further arguments. This is the correct way to call objc_msgSend by hand for this specific method; a different method with different argument types or a non-object return would require a different, equally exact cast.

id (*typedMsgSend)(id, SEL) = (id (*)(id, SEL))objc_msgSend;

The cache belongs to the CLASS, not the instance -- so the very first lookup for Circle's -area, triggered by circle1, benefits every later Circle instance, including circle2, which never needs to walk the method list at all. This is the concrete mechanic behind the 'Method Cache' and 'Method Lookup' stages of the dispatch diagram.

// 2. Check Circle's method cache for @selector(area) -> HIT

This is exactly why direct objc_msgSend calls are fragile: the compiler already knows, from the method's real declared return type, which entry point and calling convention to use for you. Hand-writing a direct call for a struct-returning method requires reproducing that same choice correctly yourself, with no compiler check to catch getting it wrong.

CGRect box = [metrics boundingBox]; // struct return -- a different calling convention than a plain id return

The per-class cache is already very cheap, but grabbing the IMP once with methodForSelector: and calling it directly bypasses even that per-call cache check for the rest of the loop -- a technique reserved for genuinely hot inner loops, not ordinary code, and one that still requires the exact same correct function-pointer cast direct objc_msgSend calls need.

double (*areaIMP)(id, SEL) = (double (*)(id, SEL))[circle methodForSelector:areaSel];

Even though [shape area] is a single line of source code invoked with a mix of Circle and Square receivers, dispatch stays fast on every call, because Circle's cache and Square's cache are entirely separate structures -- each class 'remembers' its own resolution for -area independently, which is exactly what keeps polymorphic call sites cheap under this model.

NSLog(@"%f", [shape area]); // same call site, two independent per-class caches

// Source you write:
Counter *counter = [[Counter alloc] init];
[counter increment];
[counter addValue:5];

// What the compiler generates, conceptually (the real generated code
// includes the correct casts automatically -- this is the shape of it):
//
//   Counter *counter =
//       ((id (*)(id, SEL))objc_msgSend)(
//           ((id (*)(id, SEL))objc_msgSend)(
//               (id)[Counter class], @selector(alloc)),
//           @selector(init));
//
//   ((void (*)(id, SEL))objc_msgSend)(counter, @selector(increment));
//   ((void (*)(id, SEL, NSInteger))objc_msgSend)(counter, @selector(addValue:), 5);
#import <objc/message.h>

id obj = [[NSObject alloc] init];
SEL sel = @selector(description);

// -description returns an NSString * (an object) and takes no extra
// arguments, so the correct function-pointer type is: id (*)(id, SEL)
id (*typedMsgSend)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
NSString *desc = typedMsgSend(obj, sel);
NSLog(@"%@", desc);
// First send of -area to ANY Circle instance:
//   1. objc_msgSend(circle1, @selector(area))
//   2. Check Circle's method cache for @selector(area) -> MISS
//   3. Walk Circle's own method list -> found -area, IMP = Circle_area_IMP
//   4. Circle's cache is updated: @selector(area) -> Circle_area_IMP
//   5. Jump to Circle_area_IMP(circle1, @selector(area))

// Second send of -area, to a DIFFERENT Circle instance:
//   1. objc_msgSend(circle2, @selector(area))
//   2. Check Circle's method cache for @selector(area) -> HIT
//   3. Jump directly to Circle_area_IMP(circle2, @selector(area))
//      (no method-list walk, no superclass walk at all)
@interface ShapeMetrics : NSObject
- (CGRect)boundingBox;
@end

// Source-level send:
ShapeMetrics *metrics = [[ShapeMetrics alloc] init];
CGRect box = [metrics boundingBox];

// On calling conventions where a struct this size doesn't fit in the
// registers an ordinary id return uses, the compiler does NOT emit a
// plain objc_msgSend call for this -- it emits a call to the struct-
// return entry point instead, passing a hidden pointer to caller-
// allocated storage for the CGRect as an extra, implicit first
// argument, because returning a multi-field struct follows a
// different calling convention than returning a single object pointer.
Circle *circle = [[Circle alloc] init];
SEL areaSel = @selector(area);

// methodForSelector: performs lookup ONCE and hands back the resolved
// IMP directly, cast to match the method's real signature.
double (*areaIMP)(id, SEL) =
    (double (*)(id, SEL))[circle methodForSelector:areaSel];

double total = 0;
for (int i = 0; i < 1000000; i++) {
    total += areaIMP(circle, areaSel);   // no cache lookup at all, here
}
@interface Circle : NSObject
@property (nonatomic) double radius;
- (double)area;
@end
@interface Square : NSObject
@property (nonatomic) double side;
- (double)area;
@end

@implementation Circle
- (double)area { return 3.14159 * self.radius * self.radius; }
@end
@implementation Square
- (double)area { return self.side * self.side; }
@end

NSArray<NSObject *> *shapes = @[circle, square, circle, square];
for (id shape in shapes) {
    // Same source-level send site every iteration, but Circle and
    // Square each maintain their OWN method cache -- every call here
    // hits a fast, per-class cache, never a single shared table.
    NSLog(@"%f", [shape area]);
}

The objc_msgSend Dispatch Path

Every message send follows this exact path: source syntax becomes an objc_msgSend call carrying the receiver and SEL, which are used to find the receiver's class, whose cache is checked first (fast path); a miss falls through to full method lookup (slow path); either way, the result is an IMP the runtime jumps to as the actual implementation.

[object doSomething]
        ↓
objc_msgSend
        ↓
Receiver
        ↓
SEL
        ↓
Class
        ↓
Method Cache
        ↓
Method Lookup
        ↓
IMP
        ↓
Implementation

Where Direct objc_msgSend Calls Actually Show Up

Application code essentially never calls objc_msgSend directly -- ordinary bracket message-send syntax already generates the correct, exactly-cast call for every method, every time, with zero chance of a mismatched signature. Direct calls to objc_msgSend (and its ABI-variant siblings) show up almost exclusively in three places: the Objective-C runtime's own internals, very low-level performance-sensitive libraries that have already measured a real bottleneck and are working around it deliberately, and tooling that operates below the language layer entirely, such as debuggers, profilers, and dynamic instrumentation frameworks that need to intercept or replay message sends. Outside of those contexts, reaching for a direct objc_msgSend call is virtually always a sign that ordinary message syntax, performSelector:, or an NSInvocation would be the safer and, in the overwhelming majority of cases, equally fast choice.

float (*typedMsgSend)(id, SEL) = (float (*)(id, SEL))objc_msgSend; float result = typedMsgSend(obj, @selector(computeRatio));

// On platforms/architectures where floating-point returns use a // different calling convention, cast to and call through the correct // floating-point-return entry point instead of plain objc_msgSend: #import <objc/message.h> float (*typedMsgSend)(id, SEL) = (float (*)(id, SEL))objc_msgSend_fpret; float result = typedMsgSend(obj, @selector(computeRatio));

A float- or double-returning method can require a different entry point than a plain object-returning send on calling conventions where floating-point return values use separate registers from an ordinary id return. Casting plain objc_msgSend to a float-returning function type isn't enough on those conventions -- the entry point itself has to match, which is exactly why hand-written direct calls are fragile and best avoided.

void (*typedMsgSend)(id, SEL, id) = (void (*)(id, SEL, id))objc_msgSend; typedMsgSend(obj, @selector(setName:andAge:), name); // missing the 'andAge:' argument entirely void (*typedMsgSend)(id, SEL, id, NSInteger) = (void (*)(id, SEL, id, NSInteger))objc_msgSend; typedMsgSend(obj, @selector(setName:andAge:), name, age);

The cast's argument list must match the real method's full argument list exactly, in count and type. Omitting an argument (or getting one's type wrong) still compiles, because the compiler trusts the cast completely, but the call reads garbage from whatever register or stack slot the missing argument should have occupied -- exactly the register/stack corruption this lesson warns about.

What does the compiler translate an ordinary message send like [object doSomething] into, at the level this lesson describes? A. A jump table lookup using a fixed numeric index assigned to doSomething at compile time B. A call to the C function objc_msgSend, passing the receiver and the selector (plus any further arguments) as its arguments C. An inline expansion of the method's body directly at the call site D. A call into the Objective-C compiler itself, which resolves the method live during program execution

B. A call to the C function objc_msgSend, passing the receiver and the selector (plus any further arguments) as its arguments — Message-send syntax is pure sugar over a call to the runtime function objc_msgSend, which receives the receiver and selector as its first two arguments, with any further message arguments appended after them.

What does objc_msgSend check first when resolving a message send? A. The receiver's own method cache, checked before determining its class B. The receiver's class's method cache C. The full superclass chain, from the root class downward D. Whether the selector exists anywhere at all in the entire runtime, across all classes

B. The receiver's class's method cache — After identifying the receiver's class, objc_msgSend checks that class's own method cache for the incoming selector first -- this is the fast path, and it's checked before any method list or superclass chain is ever consulted.

What happens on a method cache miss? A. The message send fails immediately with 'unrecognized selector' B. The runtime walks the receiver's own class's method list, then each superclass's method list in turn, looking for a match; if found, the result is cached for next time C. The runtime falls back to sending the message to the receiver's delegate instead D. The compiler recompiles the class at runtime to add the missing method

B. The runtime walks the receiver's own class's method list, then each superclass's method list in turn, looking for a match; if found, the result is cached for next time — A cache miss triggers the slow path: a method-list walk on the receiver's own class, then up the superclass chain until a match is found (or the chain is exhausted, triggering dynamic resolution and forwarding). Once found, the resulting IMP is written into the receiver's class's cache for future sends.

Why must a direct call to objc_msgSend be cast to the exact correct function pointer type before it's called? A. It's purely a style convention with no functional effect B. objc_msgSend's own declared signature is a generic placeholder; calling it without the exact matching return and argument types causes the underlying call to read or write the wrong registers or stack slots C. Without a cast, objc_msgSend refuses to compile at all D. The cast is only needed for methods that take zero arguments

B. objc_msgSend's own declared signature is a generic placeholder; calling it without the exact matching return and argument types causes the underlying call to read or write the wrong registers or stack slots — objc_msgSend's generic declared signature exists so the compiler can generate the correct exact call for ordinary message syntax automatically. Calling it directly bypasses that automatic correctness, so the caller must supply an exactly matching cast themselves, or the call corrupts the underlying register/stack layout.

What is objc_msgSend_stret for, conceptually? A. Sending a message specifically to a struct instance, as opposed to an object B. Methods whose return value is a struct too large to fit in the registers an ordinary object return uses -- such calls pass a hidden pointer to caller-allocated storage as an extra, implicit argument C. A faster variant of objc_msgSend used only in Release builds D. Sending a message to the superclass instead of the receiver's own class

B. Methods whose return value is a struct too large to fit in the registers an ordinary object return uses -- such calls pass a hidden pointer to caller-allocated storage as an extra, implicit argument — objc_msgSend_stret exists for the different calling convention large struct returns require: the caller allocates space for the result and passes a pointer to it as an extra implicit argument, rather than the value coming back through the ordinary object-return registers.

Why do polymorphic call sites (the same message send, invoked across instances of different classes over a program's lifetime) stay fast under Objective-C's dispatch model? A. Because Objective-C avoids polymorphism entirely by resolving all sends at compile time B. Because each class maintains its own independent method cache, so every class involved gets its own fast cache hit rather than sharing one lookup table that would have to distinguish between classes on every call C. Because objc_msgSend recompiles the call site into a fixed jump the first time it runs, permanently D. Because polymorphic call sites always fall back to the slow method-list walk, but that walk is cheap enough not to matter

B. Because each class maintains its own independent method cache, so every class involved gets its own fast cache hit rather than sharing one lookup table that would have to distinguish between classes on every call — Method caches belong to individual classes, not to call sites or a single global table -- so a call site invoked with, say, both Circle and Square receivers gets a fast, independent cache hit for each class involved, keeping the site fast without needing a shared, class-discriminating lookup structure.

A class Logger declares - (NSInteger)logCount; (returns an NSInteger, no extra arguments) and - (void)logMessage:(NSString *)message level:(NSInteger)level; (returns void, takes an object and an NSInteger). Write the exact, correctly cast direct objc_msgSend call for each of these two methods, matching the pattern shown in this lesson's examples, and explain in a comment why each cast has the shape it does.

NSInteger (*logCountIMP)(id, SEL) = (NSInteger (*)(id, SEL))objc_msgSend;\nNSInteger count = logCountIMP(logger, @selector(logCount));\n// Return type is NSInteger (not an object), no extra arguments beyond self/SEL.\n\nvoid (*logMessageIMP)(id, SEL, NSString *, NSInteger) =\n (void (*)(id, SEL, NSString *, NSInteger))objc_msgSend;\nlogMessageIMP(logger, @selector(logMessage:level:), @"Started", 2);\n// Return type void; two declared arguments (NSString *, NSInteger) appended\n// after (id, SEL), in the exact order and types the method declares them.

The cast's return type must match the method's real return type exactly (NSInteger resolves to a plain C integer type, not an object), and its argument list, after (id, SEL), must list every declared argument in order with its real type -- here, an object pointer followed by an NSInteger for the second method.