Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the objc runtime function `class_addMethod()` adds the implementation as instance and class method when the target class is `NSObject`?

When I use objc runtime function class_addMethod() to inject an implementation to NSObject's instance selector, it actually inject the implementation to the instance selector AND the class selector:

@implementation HelloWorldClass
- (void) helloWorld{
    NSLog(@"hello world from instance method -helloWorld");
}
@end

// ====
// Do method injection when the application did finish launching.
Class sourceClass = objc_getClass("HelloWorldClass");
Class targetClass = objc_getClass("NSObject");
SEL helloWorldSelector = @selector(helloWorld);

Method method = class_getInstanceMethod(sourceClass, helloWorldSelector);
IMP imp = method_getImplementation(method);
const char *methodTypeEncoding = method_getTypeEncoding(method);

class_addMethod(targetClass, helloWorldSelector, imp, methodTypeEncoding);

Now we just declare the interface of helloWorld via Objc Category, and invoke the helloWorld message to both NSObject instance and class:

// Declare the interface for `helloWorld
@interface NSObject (HelloWorld)
+ (void) helloWorld;
- (void) helloWorld;
@end


// Send the `helloWorld` message to NSObject class
NSLog(@"Send the `helloWorld` message to NSObject class");
[NSObject helloWorld];

// Send the `helloWorld` message to NSObject instance
NSLog(@"Send the `helloWorld` message to NSObject instance");
[[NSObject new] helloWorld];

Although you just injected the helloWorld implementation to the NSObject instance selector via class_addMethod(), but both class and instance messages are resolved after injection:

=> Send the `helloWorld` message to NSObject class
=> hello world from instance method -helloWorld
=> Send the `helloWorld` message to NSObject instance
=> hello world from instance method -helloWorld

After testing, I found that class_addMethod() adds the implementation to both class and instance selectors only when the target class of class_addMethod() is NSObject.

Is it a bug for objc-runtime or Cocoa?

like image 289
Xaree Lee Avatar asked Nov 30 '13 13:11

Xaree Lee


1 Answers

Nope, it's not a bug. It's defined (albeit obscure) behavior of the runtime system.

Just as every instance has an isa instance variable that points to it's class, every class structure in memory has an isa member that points to its metaclass. And just as any given class contains metadata about its instances -- including the list of methods that instances respond to -- the class's metaclass contains metadata about the class itself, including the list of methods that the class responds to.

In addition, every class structure has a superclass member that points to its superclass, which is mirrored in the metaclass hierarchy (i.e., each metaclass's superclass is another metaclass).

There's one major difference though: the superclass of NSObject is nil, while the superclass of the NSObject metaclass is NSObject. In other words, NSObject's metaclass inherits NSObject's instance methods. As a consequence, Objective-C classes not only respond to their defined class methods, they also respond to NSObject instance methods.

Confused yet? Greg Parker wrote an excellent blog that includes a very helpful diagram illustrating how this is all wired together:

Hamster Emporium archive - Classes and metaclasses

Edit

Alas, the internets. If the browser you're currently using doesn't display inline PDF documents, here's a link directly to the diagram:

Hamster Emporium archive - class diagram

like image 99
jlehr Avatar answered Sep 20 '22 07:09

jlehr