Objective-C

SEL, IMP, Method: The Three Core Elements of Objective-C Runtime — A Deep Dive

By Seren  |  09 Sep, 2026  |  Leave a comment


SEL, IMP, and Method are the three pillars of Objective-C message-sending system. Understanding how they interact at the Runtime level explains why method calls work the way they do. The names made sense on the surface — Method seems like a method, IMP seems like an implementation, SEL seems like a selector — but what they actually did and how they differed was a complete blur. It wasn’t until I started doing method swizzling and dynamically adding methods that I finally understood the relationship between them. It’s essentially a dictionary lookup process.

The Underlying Nature of SEL

In the runtime source code, SEL is defined extremely simply:

typedef struct objc_selector *SEL;

It’s just a pointer to an objc_selector structure. However, in Apple’s runtime source code, the objc_selector structure doesn’t have publicly exposed members — it’s essentially an “opaque type.” SEL is essentially a C string wrapped at the runtime layer.

When a method is compiled, the compiler generates a unique SEL and registers it in the runtime’s global hash table. The table’s key is the string, and the value is the SEL pointer. The sel_registerName function handles this: if the string is already in the table, it returns the corresponding SEL; if not, it creates a new SEL and inserts it.

// From objc4 runtime source (objc_runtime_new.h)
typedef struct objc_selector *SEL;

// sel_registerName is the core function:
// If the string exists in the global hash table, return the corresponding SEL
// If not, create a new SEL and insert it
SEL sel_registerName(const char *str);

SEL only cares about the name, not ownership. No matter whether two classes have an inheritance relationship or not, as long as they share the same method name, they share the same SEL. For example, @selector(viewDidLoad) returns the same SEL address whether you’re on a ViewController or its subclass. It’s just a globally unique string identifier — completely unrelated to “who owns this method.”

SEL lookup is an O(1) hash lookup, not string comparison. Many people assume SEL is a string and method lookup is strcmp — that’s a misconception. The runtime uses a global table to map all method names to SEL pointers. During message sending, the comparison is on the numerical value of the SEL pointer, not string traversal. This is a critical performance optimization.

SEL is the natural barrier to method overloading. If you write two methods with the same name but different parameter types — for example, - (void)setWidth:(int)width and - (void)setWidth:(double)width — the compiler can’t distinguish them because their SELs are identical. Objective-C sidesteps method overloading at the language level, and this SEL uniqueness is the reason. This is also why Swift can support method overloading but Objective-C cannot.

The Underlying Nature of IMP

IMP is defined as:

// From objc4 runtime source (objc.h)
// IMP is a function pointer: receiver + selector + optional arguments
typedef id (*IMP)(id self, SEL _cmd, ...);

// Where self and _cmd come from:
// objc_msgSend pushes the receiver (self) and selector (_cmd)
// as the first two arguments onto the stack before jumping to the IMP

IMP is a function pointer that points to the starting address of a method’s implementation. After compilation, Objective-C methods become C functions. Where do self and _cmd inside a method come from? objc_msgSend pushes the receiver and selector as the first two arguments onto the stack before jumping to the IMP. What’s _cmd used for inside a method? It can distinguish which selector is calling the same method when multiple selectors point to the same implementation — you can use _cmd to identify the specific caller.

The Underlying Nature of Method

In the runtime source code, Method is defined as:

// From objc4 runtime source (objc_runtime_new.h)
struct objc_method {
    SEL method_name;           // Method name (the "index" in the method table)
    const char *method_types;  // Type encoding string (parameter + return types)
    IMP method_imp;            // Function pointer (the actual implementation)
};

// method_types examples:
// "v@:"  = void return, id self, SEL _cmd
// "i@:@i" = int return, id self, SEL _cmd, id param, int param

Method is a struct that packages a SEL, an IMP, and method type encoding together. It’s the complete description of a method: what it’s called (SEL), how it’s called (type encoding), and what its implementation is (IMP).

The method type encoding (method_types) is a string that describes the parameter types and return type. "v@:" means void return, id self, SEL _cmd. "i@:@i" means int return, id self, SEL _cmd, id parameter, int parameter.

// Print a method's type encoding
Method method = class_getInstanceMethod([NSString class], @selector(appendString:));
const char *types = method_getTypeEncoding(method);
NSLog(@"%s", types);  // Outputs something like "v@:@"

The Collaborative Relationship

objc_msgSend‘s lookup process is the complete collaboration flow of these three elements:

1. Receive receiver and SEL
2. Find the class object through receiver->isa
3. Look up SEL in the class's method cache
   → Hit → Get IMP directly → Jump and execute
   → Miss → Traverse the method list
4. Iterate through Methods in the method list, comparing method_name to SEL
5. Once a matching Method is found, extract method_imp
6. Jump to IMP and execute
7. Store the SEL-IMP pair in the cache

Method is the “entry” in the method table, SEL is the “index,” and IMP is the “data.” A class’s method table is essentially a SEL→IMP mapping table, but each mapping also carries type encoding information — so it’s carried by the Method struct.

class_addMethod does this: packages the given SEL and IMP into a new Method, then inserts it into the class’s method table. method_exchangeImplementations simply swaps the method_imp pointers of two Methods, leaving the SEL and type encoding unchanged.

Source Code Evidence: The Collaboration During Lookup

In the lookUpImpOrForward function, you can clearly see the collaboration:

// Simplified logic from lookUpImpOrForward (objc-runtime-new.m)
IMP lookUpImpOrForward(id obj, SEL sel) {
    Class cls = obj->getIsa();           // Step 1: Get the class via isa

    // Step 2: Check the method cache first (O(1) lookup)
    IMP imp = cache_get(cls, sel);
    if (imp != NULL) return imp;         // Cache hit — return immediately

    // Step 3: Cache miss — traverse the method list
    method_list_t *methods = cls->data()->methods;
    for (method_t *m : methods) {
        if (m->method_name == sel) {     // Compare SEL pointer (not string!)
            imp = m->method_imp;         // Extract the IMP
            cache_set(cls, sel, imp);    // Store in cache for next time
            return imp;
        }
    }

    // Step 4: Not found — trigger dynamic resolution or forwarding
    return resolveMethod(cls, sel);
}

Although the actual implementation uses more complex binary search and cache optimizations, the core logic remains: use SEL to find Method in the method list, then extract the IMP from it.

What Each Concept Is Used For

SEL is used wherever you need to “represent a method name” — for example, @selector, NSSelectorFromString, -respondsToSelector:, -performSelector:, and the first argument to class_getInstanceMethod.

IMP is used when you need to “directly call a method implementation” — for example, bypassing message sending in performance-sensitive scenarios, or as an argument to class_addMethod during dynamic method resolution.

Method is used when you need to “operate on an entire method definition” — for example, method_exchangeImplementations to swap two methods, or class_replaceMethod to replace a method implementation.

Summary

The distinction between SEL, IMP, and Method:

  • SEL is the method name — a globally unique string identifier, responsible only for “what it’s called,” not “who owns it”
  • IMP is the function pointer — points to the method implementation’s starting address, responsible for “how it works” — the code that actually executes
  • Method is the struct — packages SEL, type encoding, and IMP together, representing the complete definition

The collaborative relationship: a class maintains a method table where each entry is a Method, and the system finds the corresponding Method through the SEL, then extracts the IMP and executes it. The dictionary analogy still holds — SEL is the word you’re looking up, Method is the dictionary entry, IMP is the definition.

A Real Project Scenario: Inspecting an Order Formatter

I use this Runtime trio when diagnosing dynamic dispatch in older Objective-C modules. The runnable example models an order formatter used by several callers. It resolves formatOrderID: as a selector, obtains the corresponding Method metadata, extracts the IMP, and compares a normal message send with a correctly typed direct call. Both paths return ORDER-000042.

This is deliberately a small, observable experiment. It shows the relationship among the three values without pretending that direct IMP calls are automatically faster or better architecture.

SEL Identifies a Message, Method Describes a Class Entry

A SEL identifies a selector such as formatOrderID:; it does not contain the implementation. A Method is Runtime metadata obtained for a class and selector. It provides access to the implementation pointer and type encoding. The same selector can resolve to different Methods and IMPs on different classes or after an override.

SEL selector = @selector(formatOrderID:);
Method method = class_getInstanceMethod([DemoFormatter class], selector);
IMP rawImplementation = method_getImplementation(method);
const char *encoding = method_getTypeEncoding(method);

Production code must check for missing metadata before dereferencing it. The example exits with a nonzero status if the Method, IMP, or encoding is unavailable.

Calling an IMP Requires the Exact Function Signature

An IMP is an untyped function pointer at the API boundary. Before calling it, the program casts it to a function type that includes the implicit self and _cmd parameters, followed by the declared method arguments. A mismatched return type or parameter layout is undefined behavior, not a recoverable Objective-C exception.

typedef NSString *(*FormatIMP)(id, SEL, NSNumber *);
FormatIMP implementation = (FormatIMP)rawImplementation;
NSString *direct = implementation(formatter, selector, @42);

Caching an IMP also changes semantics: the cached pointer continues to reference that implementation even if later Runtime code replaces the class method. Re-resolve it when mutation is possible, and prefer ordinary message dispatch unless measurement justifies a specialized direct-call path.

Download and Run the Complete Project

The complete macOS command-line project is available in its dedicated GitHub repository: objc-runtime-sel-imp-method-demo. Apple’s Objective-C Runtime documentation remains the authority for SEL, Method, and IMP APIs.

git clone https://github.com/2252408699/objc-runtime-sel-imp-method-demo.git
cd objc-runtime-sel-imp-method-demo
make run

A successful run prints the selector name, method encoding, both formatted results, and Results match: PASS. The repository includes the complete source, Makefile, requirements, safety notes, and cleanup command.

Seren
Seren

A developer exploring iOS, Objective-C, Swift, and other tech stacks. Here to document my learning process, pitfalls, and growing journey across new technologies.

Your email address will not be published. Required fields are marked *