Method and Runtime Metadata
Go beyond the basics of runtime introspection to work directly with the metadata the Objective-C runtime attaches to every class object — its name, its superclass pointer, its method list, and each individual Method's name, type encoding, and implementation — using class_getName, object_getClass, class_getSuperclass, class_copyMethodList, method_getName, method_getTypeEncoding, method_getImplementation, class_getInstanceMethod, and class_getClassMethod.
The earlier Runtime Introspection lesson in this course established that Objective-C classes are not compile-time-only constructs the way they are in many other languages — a Class is a real, live data structure the runtime consults at every message send, and you can ask questions of it while your program is running. This lesson is the deeper follow-up that earlier lesson promised: rather than asking whether an object responds to a selector or conforms to a protocol from the outside, this lesson goes directly at the metadata itself — the actual method list, property list, protocol list, and ivar list attached to a Class structure — and the specific runtime functions that let you read, copy, and walk each one. By the end, you will be able to look at any class at runtime and enumerate exactly what it knows how to do, one Method at a time, down to each method's selector, its C-string type encoding, and the raw function pointer that implements it.
How Metadata Is Attached to a Runtime Class
Every Objective-C class you write compiles down to a runtime Class object — an opaque pointer to a structure the Objective-C runtime maintains — and that structure does not just store a name. It holds, among other things, a pointer to its superclass (forming the inheritance chain the runtime walks during message dispatch), and pointers to several distinct lists of metadata: the class's method list (every Method it implements, each with a selector, a type encoding, and an implementation pointer), its property list (every declared @property, with its name and attribute string), its protocol list (every protocol the class declares conformance to), and its instance variable list (every ivar, with its name, type encoding, and offset). None of these lists are things you construct or manage by hand in ordinary code — the compiler builds them for you from your @interface and @implementation, and every function this lesson covers is a way of reading data the compiler already generated on your behalf.
This is the concrete shape behind the abstract idea of 'introspection' from the earlier lesson: when you ask whether an object responds to a selector, the runtime is, underneath that convenient API, walking exactly this method list (and its superclass's method list, and so on up the chain) looking for a matching selector. Working with class_copyMethodList and the per-Method accessor functions in this lesson means working with that same underlying data directly, rather than through the higher-level convenience methods.
Getting a Class's Name: class_getName
class_getName(Class cls) takes a Class and returns its name as a plain C string (const char *), reading directly from the metadata the runtime maintains for that class rather than anything you would normally need to store yourself. It is most useful in generic, reflective code — logging, debugging utilities, or a serialization layer — where you have a Class value (perhaps obtained dynamically, such as from object_getClass on some arbitrary object) and need a human-readable identifier for it without already knowing, at compile time, which class you are looking at.
object_getClass and the Metaclass Distinction
object_getClass(id obj) returns the Class of whatever object you pass it, and for an ordinary instance it behaves the same as calling the -class method most objects already respond to. The subtlety worth a dedicated callout is what happens when the id you pass in is not an ordinary instance but a Class object itself: because a Class is itself an object at the runtime level (every Class has its own Class, called its metaclass, which is what makes class methods possible — a class method is really an instance method of the metaclass), calling object_getClass on a Class returns that class's metaclass, not the class itself.
This is easy to get backward, and getting it backward produces confusing results in reflective code: [SomeClass class] called as a class method on SomeClass simply returns SomeClass itself (Class objects are documented to return themselves from -class), but object_getClass(SomeClass) — passing the Class object directly as the id argument — walks one level further up and returns SomeClass's metaclass. The two are not the same call with the same result; -class on a class object is effectively a fixed point, while object_getClass on that same class object is not. Code that needs 'the actual class of whatever this id is, treating a Class input as data rather than as an instance' should reach for object_getClass deliberately and be aware of exactly this behavior, rather than assuming it always mirrors -class.
Walking the Inheritance Chain: class_getSuperclass
class_getSuperclass(Class cls) returns the Class one level up the inheritance chain from cls, or NULL once you have walked past the root class (typically NSObject) with nothing above it. Calling it repeatedly in a loop, feeding each result back in as the next call's argument, is the standard way to enumerate a class's full ancestry at runtime — useful for diagnostic tooling that wants to print an object's entire class hierarchy, or for logic that needs to check something about every ancestor class rather than just the object's immediate, most-derived class.
Because Objective-C's runtime performs message dispatch by walking exactly this same superclass chain looking for a method implementation, class_getSuperclass gives you direct, explicit access to the same structure the runtime consults automatically and invisibly on every single message send — it turns an implicit mechanism into something you can inspect and print.
Enumerating a Class's Methods: class_copyMethodList
class_copyMethodList(Class cls, unsigned int *outCount) returns a freshly allocated C array of Method values — one for each method cls implements directly (not counting inherited methods from its superclasses, which have their own, separate method lists on their own Class objects) — and writes the array's length into the outCount pointer you provide. Because the runtime allocates this array specifically for your call, you are responsible for calling free() on it once you are done, which is the one memory-management detail every use of class_copyMethodList needs to remember, since Method values themselves are not objects the normal Objective-C memory-management rules apply to.
Once you have that array, each element is an opaque Method value, and the next section's three accessor functions are how you actually extract useful information out of each one — the array by itself only gives you a count and a way to iterate; class_copyMethodList never hands you selectors or type encodings directly.
Inspecting One Method: Name, Type Encoding, and Implementation
Given a single Method value — from class_copyMethodList's array, or from class_getInstanceMethod/class_getClassMethod covered next — three functions extract its distinct pieces of metadata. method_getName(Method m) returns the method's SEL, its selector, which is the runtime's compact, uniqued representation of the method's name. method_getTypeEncoding(Method m) returns a const char * holding the method's full type encoding string — a compact code describing the return type and every parameter's type, using the same single-character (and bracketed, for structs) encoding scheme covered in this course's Objective-C Type Encodings lesson, which lets generic runtime code (like NSInvocation) work with a method's arguments without knowing its exact C signature at compile time. method_getImplementation(Method m) returns the method's IMP — a raw C function pointer to the actual compiled code that runs when this method is invoked, which is the same IMP value that message dispatch ultimately calls after resolving a selector against a class's method list.
Together, these three functions let you take a Method obtained anywhere in this lesson's other functions and answer, precisely: what is it called, what does its full signature look like at the C ABI level, and what code actually runs when it's invoked. A common, legitimate use of method_getImplementation is caching an IMP directly to bypass the normal (already fast, but not free) selector-lookup cost of an ordinary message send, when a single method is going to be called an extremely large number of times in a tight loop.
Instance Methods vs. Class Methods: Two Distinct Lookup Functions
class_getInstanceMethod(Class cls, SEL name) looks up an instance method by selector, searching cls's method list and then walking up the superclass chain if necessary, and returns the matching Method (or NULL if none exists anywhere in the chain). class_getClassMethod(Class cls, SEL name) does the analogous lookup for a class method — and, consistent with the metaclass fact from earlier in this lesson, it works by looking up name as an instance method on cls's metaclass, since a class method really is just an instance method defined on the metaclass rather than on the class itself.
Passing the same selector name to both functions on the same class will, in the general case, return two entirely different Method values (or one NULL and one non-NULL) unless a class happens to define both an instance method and a class method with identical selectors, which does occasionally happen deliberately (a class method named +new alongside an unrelated instance method also called new, for instance) but is otherwise coincidental. Reflective code that needs to know specifically whether a given selector exists as an instance method, as a class method, or as both, must call both functions explicitly and check each result separately — there is no single combined lookup function that searches both instance and class method lists at once.
Enumerating a Class's Metadata
class_copyMethodList only returns methods cls implements directly; walk class_getSuperclass in a loop to see inherited methods on their own class's list. object_getClass on a Class object returns its metaclass, not the class itself, which differs from calling -class on that same object. Always free() the array returned by class_copyMethodList once you are done with it.
#import <objc/runtime.h>
Class cls = [Vehicle class];
// Name and identity
const char *name = class_getName(cls); // "Vehicle"
Class metaclass = object_getClass(cls); // Vehicle's METACLASS, not Vehicle itself
// Walking the superclass chain
Class superclass = class_getSuperclass(cls); // e.g. NSObject, or NULL at the root
// Enumerating this class's own method list
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
SEL selector = method_getName(methods[i]);
const char *encoding = method_getTypeEncoding(methods[i]);
IMP implementation = method_getImplementation(methods[i]);
NSLog(@"%s : %s -> %p", sel_getName(selector), encoding, implementation);
}
free(methods); // class_copyMethodList allocates; you own the arrayCode Examples
class_getName reads the class's name directly from its runtime metadata, which is exactly what makes it useful here: the code has no compile-time way of knowing whether someObject is a Circle, a Square, or a Triangle, but class_getName reports the correct name regardless, because that name lives in the Class structure itself rather than in this call site's source code. Expected result: someObject's class is: Circle
For an ordinary instance, -class and object_getClass agree completely, both returning Vehicle. For the Class object Vehicle itself, though, -class returns Vehicle unchanged (a documented fixed point for class objects), while object_getClass returns one level further up: Vehicle's metaclass. The two calls are not interchangeable the moment the id involved is itself a Class rather than an ordinary instance. This distinction matters directly for class_getClassMethod, covered later in this lesson, which works by performing an instance-method lookup specifically against a class's metaclass.
Each iteration reads the current class's name with class_getName, then moves one level up with class_getSuperclass, stopping once class_getSuperclass returns Nil at the root of the hierarchy. This is the direct, explicit version of the same chain the runtime walks implicitly, and invisibly, on every message send that isn't satisfied by the object's own most-derived class. Expected result: SportsCar -> Car -> Vehicle -> NSObject cls = class_getSuperclass(cls);
class_copyMethodList returns only the methods Vehicle implements directly, not anything inherited from NSObject, and writes how many it found into count. Each Method's selector and type-encoding string are pulled out separately with method_getName and method_getTypeEncoding; the encoding strings shown (v16@0:8, and so on) are exactly the compact type-encoding notation this course's Objective-C Type Encodings lesson covers in depth. The array returned by class_copyMethodList is allocated by the runtime specifically for this call and must be released with free() once you're done with it, unlike ordinary Objective-C objects.
method_getImplementation returns the raw C function pointer (IMP) behind currentSpeed, which is cast to a matching C function-pointer type so it can be called directly as an ordinary C function inside a tight loop over a large collection. This pattern is a genuine, if rarely necessary, performance technique: it trades the normal message-send's per-call selector resolution for a one-time lookup, and it is only worth reaching for when profiling has shown that per-call resolution is an actual bottleneck.
IMP implementation = method_getImplementation(m);
class_getInstanceMethod searches Vehicle's own method list, then its superclass chain, for an instance method matching description, finding NSObject's inherited implementation. class_getClassMethod performs the equivalent search against Vehicle's metaclass, finding NSObject's separate class-side +description. Both happen to succeed here because NSObject defines both, but in general the two functions search entirely separate method lists and can disagree — one returning NULL while the other returns a real Method. class_getClassMethod's behavior — searching the metaclass's instance-method list rather than the class's own list — only makes sense in light of the object_getClass metaclass example earlier in this lesson: a class method really is an instance method of the metaclass.
id someObject = [self makeRandomShape]; // returns Circle, Square, or Triangle at runtime
Class cls = [someObject class];
const char *className = class_getName(cls);
NSLog(@"someObject's class is: %s", className);
// Output depends on what makeRandomShape actually returned, e.g.:
// someObject's class is: CircleVehicle *instance = [[Vehicle alloc] init];
Class classOfInstance = [instance class]; // Vehicle
Class classFromObjectGetClass = object_getClass(instance); // also Vehicle -- ordinary instance
Class classObject = [Vehicle class]; // Vehicle (a Class object)
Class classOfClassObject = [classObject class]; // still Vehicle -- -class is a fixed point
Class metaclassOfClassObject = object_getClass(classObject); // Vehicle's METACLASS, not Vehicle
NSLog(@"%s vs %s", class_getName(classOfClassObject), class_getName(metaclassOfClassObject));
// Output: Vehicle vs Vehicle <- names look identical in the log, but these are
// two genuinely different Class values; the metaclass's name string happens to
// print the same as its class's name, which is exactly why this distinction is
// easy to miss without checking the actual pointer identity, not just the name.Class cls = [SportsCar class];
NSMutableArray<NSString *> *chain = [NSMutableArray array];
while (cls != Nil) {
[chain addObject:[NSString stringWithUTF8String:class_getName(cls)]];
cls = class_getSuperclass(cls);
}
NSLog(@"%@", [chain componentsJoinedByString:@" -> "]);
// Output: SportsCar -> Car -> Vehicle -> NSObjectunsigned int count = 0;
Method *methods = class_copyMethodList([Vehicle class], &count);
for (unsigned int i = 0; i < count; i++) {
Method m = methods[i];
SEL selector = method_getName(m);
const char *encoding = method_getTypeEncoding(m);
NSLog(@"-%s [%s]", sel_getName(selector), encoding);
}
free(methods);
// Sample output:
// -startEngine [v16@0:8]
// -currentSpeed [d16@0:8]
// -setCurrentSpeed: [v24@0:8d16]SEL selector = @selector(currentSpeed);
Method m = class_getInstanceMethod([Vehicle class], selector);
IMP implementation = method_getImplementation(m);
typedef double (*CurrentSpeedIMP)(id, SEL);
CurrentSpeedIMP cachedCurrentSpeed = (CurrentSpeedIMP)implementation;
double total = 0;
for (Vehicle *vehicle in largeFleetArray) {
// Calling through the cached IMP directly skips the normal, per-call
// selector-lookup step that an ordinary [vehicle currentSpeed] performs.
total += cachedCurrentSpeed(vehicle, selector);
}SEL selector = @selector(description);
Method instanceVersion = class_getInstanceMethod([Vehicle class], selector);
Method classVersion = class_getClassMethod([Vehicle class], selector);
NSLog(@"instance -description found: %@", instanceVersion ? @"YES" : @"NO");
NSLog(@"class method +description found: %@", classVersion ? @"YES" : @"NO");
// Output:
// instance -description found: YES (inherited from NSObject)
// class method +description found: YES (NSObject also defines +description)Metadata Attached to a Runtime Class Structure
A Class is a structure with a name, a superclass pointer, a metaclass pointer, and four separate metadata lists. class_copyMethodList reads the method list; method_getName/method_getTypeEncoding/method_getImplementation each read one field off a single Method entry from that list.
+-----------------------------+
| Class object |
|-------------------------------|
| name "Vehicle" |
| superclass ----------------> Class object (NSObject)
| metaclass ----------------> Class object (Vehicle's metaclass)
|-------------------------------|
| method list ---> [ Method, Method, Method, ... ]
| property list ---> [ Property, Property, ... ]
| protocol list ---> [ Protocol, Protocol, ... ]
| ivar list ---> [ Ivar, Ivar, ... ]
+-------------------------------+
Each Method entry, individually:
Method ---> { SEL name, type encoding (const char*), IMP implementation }Important Rules
class_getName(cls) returns a class's name as a C string, read directly from its runtime metadata.
object_getClass(obj) mirrors -class for an ordinary instance, but for a Class object itself it returns that class's metaclass, not the class — a genuinely different value from -class on the same object.
class_getSuperclass(cls) walks one level up the inheritance chain, returning NULL once past the root class.
class_copyMethodList(cls, &count) returns only the methods cls implements directly (not inherited ones), as a freshly allocated array you must free().
method_getName, method_getTypeEncoding, and method_getImplementation each extract one distinct piece of metadata — selector, encoding string, and raw IMP — from a single Method value.
class_getInstanceMethod and class_getClassMethod search separate lists (the class's own method list vs. its metaclass's method list) and can return different results for the same selector name.
Where This Metadata Actually Gets Used
This exact set of functions is what powers real, shipping infrastructure that most Objective-C developers rely on without ever calling class_copyMethodList themselves: KVO's dynamic subclassing, mocking frameworks used in unit tests, and serialization/archiving libraries that need to enumerate a class's properties or methods generically all walk this same metadata using these same runtime functions under the hood. Understanding class_getName, object_getClass, class_getSuperclass, class_copyMethodList, and the per-Method accessors this lesson covers is, in a very direct sense, understanding how those tools actually work, rather than treating them as opaque magic — and it is also exactly the toolkit needed to build a custom reflective utility (a generic object-to-dictionary converter, a debug-menu class browser, a lightweight dependency-injection container) when an existing library does not already cover the specific case at hand.
Class metaclass = object_getClass(SomeClass); // then treating metaclass as if it were just SomeClass itself, e.g. assuming class_getName(metaclass) always gives the same downstream behavior as working with SomeClass directly for further reflective lookups.
Use [SomeClass class] (or simply SomeClass itself, already a Class value) when you want the class; reach for object_getClass(SomeClass) deliberately only when you specifically need that class's metaclass, such as when working with class-side method resolution.
object_getClass applied to a Class object returns its metaclass, not the class itself — a distinct Class value one level up from what -class on that same object would return. Conflating the two leads to reflective code that quietly looks up the wrong thing whenever the metaclass distinction actually matters, such as when passed into class_getInstanceMethod versus class_getClassMethod.
Method *methods = class_copyMethodList(cls, &count); ... (iterate over methods) ... with no matching free(methods) call anywhere afterward.
Method *methods = class_copyMethodList(cls, &count); ... (iterate over methods) ...; free(methods); once the array is no longer needed.
class_copyMethodList allocates a fresh C array specifically for this call, and the runtime does not free it automatically the way ordinary Objective-C objects are reclaimed by ARC — omitting free() leaks that array every single time the enumeration code runs.
What does class_getName(cls) return? A. A boolean indicating whether the class has a valid name B. The class's name as a C string, read from its runtime metadata C. An NSString object holding the class's name D. The name of the class's superclass instead of its own name
B. The class's name as a C string, read from its runtime metadata — class_getName returns a plain const char * containing the class's own name, read directly out of the Class structure's metadata rather than something the caller needs to track separately.
What does object_getClass(obj) return when obj is a Class object itself, rather than an ordinary instance? A. The class's superclass B. The class itself, identical to calling -class on it C. The class's metaclass, which is a distinct Class value one level up from the class itself D. NULL, because object_getClass only accepts ordinary instances
C. The class's metaclass, which is a distinct Class value one level up from the class itself — For an ordinary instance, object_getClass mirrors -class, but when passed a Class object directly, it returns that class's metaclass rather than the class itself — a subtle but important distinction from what -class on the same object would return.
What does class_getSuperclass(cls) return once cls is the root class (e.g. NSObject) with nothing above it? A. It returns cls again, looping indefinitely B. It returns NULL C. It throws an exception D. It returns the metaclass of cls
B. It returns NULL — class_getSuperclass returns NULL once there is no class above the current one in the inheritance chain, which is the standard loop-termination condition when walking a class's full ancestry.
Why must the array returned by class_copyMethodList be passed to free()? A. It does not need to be freed; ARC manages it automatically like any other Objective-C object B. The runtime allocates that array specifically for the call, and it is not an Objective-C object subject to ARC's normal memory management C. free() is only needed if the array has more than one element D. class_copyMethodList never actually allocates memory, so free() is a no-op
B. The runtime allocates that array specifically for the call, and it is not an Objective-C object subject to ARC's normal memory management — class_copyMethodList allocates a fresh C array on each call, and unlike ordinary Objective-C objects, that array is not tracked or released by ARC — the caller is responsible for calling free() once done with it, or the array leaks.
What is the key difference between class_getInstanceMethod and class_getClassMethod for the same selector name? A. They are identical functions with different names for convenience B. class_getInstanceMethod searches the class's own method list (and its superclass chain); class_getClassMethod searches the class's metaclass's method list instead, since a class method is an instance method of the metaclass C. class_getClassMethod only works on root classes like NSObject D. class_getInstanceMethod always returns NULL for class methods, and vice versa, so they never need to both be checked
B. class_getInstanceMethod searches the class's own method list (and its superclass chain); class_getClassMethod searches the class's metaclass's method list instead, since a class method is an instance method of the metaclass — class_getInstanceMethod looks up an instance method directly on the class (and up its superclass chain); class_getClassMethod performs the analogous lookup against the class's metaclass, consistent with the fact that a class method is really an instance method defined on the metaclass rather than on the class itself.
Write a function that takes a Class and logs every selector it implements directly, alongside whether an instance method or a class method (or both) exists for that exact selector name elsewhere on the class, without assuming in advance which selectors the class defines.
unsigned int count = 0; Method *methods = class_copyMethodList(cls, &count); for (unsigned int i = 0; i < count; i++) { SEL selector = method_getName(methods[i]); BOOL hasInstance = class_getInstanceMethod(cls, selector) != NULL; BOOL hasClassMethod = class_getClassMethod(cls, selector) != NULL; NSLog(@"%s -- instance: %@, class: %@", sel_getName(selector), hasInstance ? @"YES" : @"NO", hasClassMethod ? @"YES" : @"NO"); } free(methods); This enumerates the class's own instance-method list via class_copyMethodList, then, for each selector found, separately checks both class_getInstanceMethod and class_getClassMethod against the same class, since the two functions search entirely different lists (the class's own vs. its metaclass's) and neither call alone answers both questions.
class_copyMethodList only gives you the selectors implemented directly as instance methods; for each selector you find that way, you still need to explicitly call class_getInstanceMethod and class_getClassMethod on the same class to check both sides for that same selector name.