Dynamic Method Resolution
Learn the class-level hooks the Objective-C runtime calls when ordinary method lookup fails, and how to supply method implementations dynamically with class_addMethod, class_replaceMethod, and class_getInstanceMethod.
The previous lesson traced the normal path a message send takes: follow isa to the class, search the method list, walk superclass pointers, find an IMP, dispatch. But what happens when that whole search comes up empty — when no class in the chain has ever implemented a method for the selector being sent? Ordinary dispatch has one more trick before it gives up: it asks the class, directly, whether it would like to supply an implementation right now, on the spot. That question is dynamic method resolution, and this lesson covers the two hook methods that answer it, the runtime functions used to attach a method on demand, and — just as importantly, since the spec calls this out explicitly — when this technique earns its complexity and when it just makes code harder to trust.
The hook: resolveInstanceMethod: and resolveClassMethod:
When objc_msgSend's normal lookup fails to find a matching SEL anywhere in the receiver's class hierarchy, the runtime does not immediately move to message forwarding. First, for an instance method lookup, it sends +resolveInstanceMethod: to the receiver's class, passing the SEL that could not be found. For a class method lookup, the analogous hook is +resolveClassMethod:, sent to the metaclass. Both are class methods you override to say, in effect, 'actually, I can supply an implementation for that selector right now' — your one chance to add a real method before the runtime falls back further.
The default implementation of both methods, inherited from NSObject, simply returns NO, meaning 'I have nothing to offer,' which lets the runtime proceed to message forwarding (covered in a later module) and, if that also fails, ultimately raise unrecognized selector sent to instance. Overriding resolveInstanceMethod: to actually add a method and return YES is what turns this failure path into a genuine, working message send instead of a crash.
Adding a method at runtime with class_addMethod
Inside a resolveInstanceMethod: override, the usual move is to call class_addMethod, a libobjc C function that attaches a new SEL-to-IMP pairing directly onto a class, as if that method had been compiled into the class from the start. Its signature takes the class to modify, the SEL to add, a C function pointer to serve as the IMP, and a type-encoding string describing the method's argument and return types (the same kind of encoding the compiler normally generates for you from your method's declared signature).
class_addMethod returns YES if it successfully added the method and NO if the class already had an implementation for that selector (in which case adding would have been a no-op or a conflict). Inside resolveInstanceMethod:, you typically call class_addMethod and then return whatever it returns — if it succeeded, the runtime immediately retries the original message send, this time finding the freshly added method and dispatching to it normally.
Replacing an existing method with class_replaceMethod
class_replaceMethod serves a related but distinct purpose: rather than adding a method where none existed, it replaces the IMP for a SEL that a class already implements (or adds it if the class did not have one, making it a safe superset of class_addMethod's behavior). It is used less often inside resolveInstanceMethod: and more often in scenarios where you deliberately want to swap out a method's behavior at runtime.
It is worth being precise about the relationship to method swizzling here without re-teaching it: swizzling, covered as its own later module, is a specific technique typically built using method_exchangeImplementations (or manually reading one IMP with class_getMethodImplementation and installing it in place of another with class_replaceMethod or method_setImplementation) to swap two existing methods' implementations, usually to intercept and extend behavior while still calling the original. class_replaceMethod by itself is just the primitive operation 'install this IMP for this SEL on this class' — swizzling is one particular, disciplined way of using that primitive along with its sibling functions, not a synonym for it.
Looking up methods with class_getInstanceMethod
class_getInstanceMethod looks up and returns a Method — a runtime structure describing an existing method on a class (or one it inherits), rather than an IMP by itself. A Method value is useful for introspection (asking for its name, its type encoding, or its current IMP via method_getImplementation) and, notably, for borrowing an implementation: you can look up a Method on one class with class_getInstanceMethod, extract its IMP and type encoding, and install that same IMP onto a different class or a different selector with class_addMethod or class_replaceMethod.
This is a common building block for the proxy-style dynamic classes shown in the worked example below: rather than writing out a raw function pointer and type-encoding string by hand for every dynamically added method, you locate an existing, already-correct Method elsewhere (often on a related class, or from a class you generate at runtime) and reuse its IMP directly.
Worked example: synthesizing accessors on demand
A realistic use of this whole mechanism is a class that represents a flexible, dynamically defined set of properties — think of a lightweight model object backed by a dictionary, where the exact set of property names is not known until runtime (for example, columns pulled from a server response). Rather than hand-writing a getter and setter for every possible key in advance, such a class can override resolveInstanceMethod:, recognize that the failed selector looks like a getter (say, its name matches a key in the backing dictionary), and use class_addMethod to attach a small, generic IMP that looks the key up in the dictionary and returns it — installed under the exact SEL that was originally sent, so future calls to that same accessor hit the newly added method directly (and benefit from the method cache) instead of going through resolveInstanceMethod: every time.
The same shape shows up in proxy-style objects: a class that does not itself implement most of the methods it appears to support, but instead resolves them lazily on first use, often by discovering at runtime (through introspection) that some other object or data source knows how to satisfy the request, then wiring up an IMP that forwards to it. In both cases, the point of the pattern is the same: defer the cost and complexity of defining every possible method up front, and only pay for the ones actually used, at the price of that first, slower resolution step.
When it's useful, and why it demands care
Dynamic method resolution earns its place in a small number of genuine situations. The canonical one is implementing @dynamic properties backed by storage other than a plain instance variable — the textbook example being Core Data, where a managed object's properties are declared @dynamic and their actual getters and setters are supplied at runtime, resolved against the managed object's underlying storage rather than compiled in ahead of time, because the concrete set of attributes is only known once a model is loaded. The other broad case is building deliberately dynamic or proxy-like objects, such as the accessor-synthesis and forwarding examples above, where the whole value of the design is not needing to know every message a receiver must handle at compile time.
Outside cases like these, dynamic method resolution should be reached for carefully, and the spec's own wording — 'used carefully' — is worth taking literally. It bypasses compile-time safety entirely: the compiler cannot check that a selector resolved this way actually exists, takes the right arguments, or returns the right type, because as far as the compiler is concerned, nothing was ever declared. It also makes code substantially harder to read, debug, and step through — a method implemented via resolveInstanceMethod: does not appear in the class's source in any conventional form, so a reader (or a debugger's step-into) following a call has to already know to look for a runtime hook rather than an ordinary method definition. Finally, mistakes here do not show up as compiler warnings; they surface only as runtime crashes, typically the same unrecognized selector sent to instance error dynamic resolution exists to prevent, but now triggered by a subtler bug (a mismatched type encoding, a resolver that returns NO when it should return YES, or a key that does not actually exist in the backing store) that is much harder to track down than an ordinary missing-method error would have been.
Code Examples
sparkle was never written anywhere as an ordinary method. The first time it is sent, normal lookup fails, the runtime calls resolveInstanceMethod:, and the override attaches dynamicGetter as its IMP with type encoding "v@:" (void return, id self, SEL _cmd) before returning YES, so the runtime retries and succeeds.
class_addMethod([self class], sel, (IMP)dynamicGetter, "v@:");
Common mistake: Forgetting to return YES after a successful class_addMethod leaves the runtime thinking resolution failed, and it proceeds to forwarding or a crash anyway.
class_replaceMethod installs a brand-new IMP for an existing SEL directly, discarding the previous mapping (its return value is the previous IMP, which the caller must manage explicitly if it wants to preserve it). This is the raw primitive; disciplined 'replace but still call through to the original' swizzling is built from this primitive plus method_exchangeImplementations, covered later.
class_replaceMethod([Logger class], @selector(logMessage), (IMP)loggingImplementation, method_getTypeEncoding(original));
class_getInstanceMethod returns a Method structure (or NULL if the class truly has no such method, inherited or otherwise), from which the type encoding and current IMP can both be read. Here NSObject's description implementation is borrowed wholesale and installed under a different selector on an unrelated class — a common trick in proxy-style dynamic classes.
Method m = class_getInstanceMethod([NSObject class], @selector(description));
Calling [super resolveInstanceMethod:sel] and ignoring its return value, or always returning YES regardless of whether a method was actually added.
Return YES only when class_addMethod (or an equivalent) actually succeeded for the given selector, and fall through to [super resolveInstanceMethod:sel] for any selector your override does not recognize.
Returning YES for a selector you did not actually add tells the runtime resolution succeeded, so it retries the send — finds nothing new was added — and the behavior becomes confusing and inconsistent rather than a clean unrecognized-selector failure or a working dispatch.
Getting the type encoding string passed to class_addMethod wrong, or guessing at it instead of deriving it correctly.
Match the type encoding to the IMP's real C function signature exactly (return type, self, _cmd, and each argument), or copy it from an existing Method via method_getTypeEncoding when reusing another method's shape.
A mismatched type encoding does not necessarily fail immediately — it can produce silent memory corruption or crashes that are far harder to diagnose than a straightforward compiler type error, because the mistake only manifests once the dynamically added method is actually called.
Using dynamic method resolution as a routine way to avoid declaring methods, in ordinary application code with a fixed, known set of behaviors.
Reserve dynamic method resolution for cases where the set of methods genuinely cannot be known at compile time — @dynamic properties backed by non-standard storage, or deliberately proxy-like objects — and write ordinary methods everywhere else.
The spec's own caution applies directly here: this technique bypasses compile-time safety, makes stepping through code harder, and turns what would have been compiler errors into runtime crashes — costs worth paying only when the dynamism is the actual point of the design.
Confusing class_replaceMethod with method swizzling, or assuming they are interchangeable terms for the same operation.
Treat class_replaceMethod as the low-level primitive that installs an IMP for a SEL; treat swizzling as a specific, disciplined pattern (typically built on method_exchangeImplementations) for swapping two methods' implementations while usually still calling through to the original.
Using class_replaceMethod the way swizzling is normally used, without preserving and calling the original implementation, silently deletes the original behavior instead of extending it — the two are related but solve different problems, and the later swizzling module covers the safer pattern in full.
When does the runtime call +resolveInstanceMethod:? A. Before every message send, as a routine check B. Only when the app first launches C. When ordinary method lookup for an instance message has failed to find a matching selector anywhere in the class hierarchy D. Only if the receiver is nil
C. When ordinary method lookup for an instance message has failed to find a matching selector anywhere in the class hierarchy — resolveInstanceMethod: is the runtime's hook after normal lookup (searching the class and walking superclass pointers) has already failed to find the selector, and before it moves on to message forwarding.
What does class_addMethod actually do? A. It deletes an existing method from a class B. It attaches a new SEL-to-IMP pairing onto a class, as if it had been compiled in C. It creates an entirely new subclass D. It changes a method's type encoding without altering its IMP
B. It attaches a new SEL-to-IMP pairing onto a class, as if it had been compiled in — class_addMethod dynamically installs a new method (a SEL paired with an IMP and a type encoding) on a class at runtime, typically from inside a resolveInstanceMethod: override.
How does class_replaceMethod differ from class_addMethod? A. class_replaceMethod only works on class methods, class_addMethod only on instance methods B. class_replaceMethod installs an IMP for a SEL the class may already implement, replacing the existing implementation (or adding it if absent), rather than only adding where nothing existed C. They are identical in every respect D. class_replaceMethod cannot be used with a type-encoding string
B. class_replaceMethod installs an IMP for a SEL the class may already implement, replacing the existing implementation (or adding it if absent), rather than only adding where nothing existed — class_replaceMethod is the more general primitive: it replaces an existing SEL's IMP if one exists, or adds it if not, whereas the described purpose of class_addMethod is adding where nothing previously existed.
What does class_getInstanceMethod return, and why is that useful? A. It returns a raw IMP directly, with no other information B. It returns a Method structure describing an existing method, from which you can read the type encoding and current IMP, or borrow that IMP for reuse elsewhere C. It returns a Boolean indicating whether the class responds to a selector D. It permanently removes the method it looks up
B. It returns a Method structure describing an existing method, from which you can read the type encoding and current IMP, or borrow that IMP for reuse elsewhere — class_getInstanceMethod looks up a Method structure (not just an IMP), which exposes both the type encoding and the current implementation — useful for introspection and for reusing an implementation on another class or selector.
According to this lesson, why must dynamic method resolution be used carefully? A. Because it is slower than every other Objective-C feature B. Because it only works on iOS, not macOS C. Because it bypasses compile-time safety, makes code much harder to read and debug, and lets mistakes surface only as runtime crashes instead of compiler errors D. Because Apple has deprecated it in favor of Swift
C. Because it bypasses compile-time safety, makes code much harder to read and debug, and lets mistakes surface only as runtime crashes instead of compiler errors — The core caution is that dynamically resolved methods have no compile-time representation for the compiler to check, they are much harder to trace while reading or debugging code, and any mistake shows up only as a runtime failure rather than an early warning.
void dynamicGetter(id self, SEL _cmd) {
NSLog(@"dynamicGetter called for %@", NSStringFromSelector(_cmd));
}
@implementation DynamicWidget
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(sparkle)) {
class_addMethod([self class], sel,
(IMP)dynamicGetter, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
// [[DynamicWidget new] sparkle]; // now works, even though
// -sparkle was never declared or implemented in source.void loggingImplementation(id self, SEL _cmd) {
NSLog(@"about to log via replaced IMP");
}
Method original = class_getInstanceMethod([Logger class], @selector(logMessage));
IMP originalIMP = method_getImplementation(original);
class_replaceMethod([Logger class], @selector(logMessage),
(IMP)loggingImplementation,
method_getTypeEncoding(original));
// originalIMP is saved, but note: unlike swizzling with
// method_exchangeImplementations, this replacement does not
// automatically give loggingImplementation an easy way to call
// the original — that pattern belongs to the swizzling module.Method m = class_getInstanceMethod([NSObject class], @selector(description));
if (m != NULL) {
const char *encoding = method_getTypeEncoding(m);
IMP existingIMP = method_getImplementation(m);
NSLog(@"description encoding: %s", encoding);
// Reuse that same IMP under a new selector on a different class:
class_addMethod([ProxyObject class], @selector(fallbackDescription),
existingIMP, encoding);
}