Add Methods at Runtime in Objective-C
Andy Finnel posted some ideas for Objective-C features beyond those slated for Leopard. One of them is adding methods at runtime. It turns out this has been possible in Mac OS X since day one, it's just not obvious how to do so.Here's what Andy says:
My previous post about Key-Value Coding and being able dynamically add transient attributes to NSManagedObject got me thinking. Why can’t I dynamically add methods? I don’t mean on just NSManagedObjects, I mean on NSObjects. I realize that categories allow that somewhat, but only as a group of functions and only at compile time.
First thing to clear up here is that categories can be loaded at runtime. Quoth the Bundle Loading documentation:
The executable code files in loadable bundles hold class (and category) definitions that the NSBundle object can dynamically load while the application runs.
But, come on, that's the easy way out. Let's dig into the Objective-C runtime. There are two structures we need to deal with:
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
struct objc_method_list {
struct objc_method_list *obsolete;
int method_count;
struct objc_method method_list[1];
};
This looks kinda scary, but it's actually not too bad. The steps are basically:
0. Make a function
1. Create an objc_method instance
2. Register the function name
3. Give the objc_method a pointer to the function
4. Add the objc_method to a objc_method_list
5. Pass the objc_method_list to class_addMethods
6. There is no step 6!
So here's all the code to do that:
#import <objc/objc-class.h>
// create a class with no methods
@interface EmptyClass : NSObject { }
@end
@implementation EmptyClass
@end
// define the function to add as a method
id sayHello ( id self, SEL _cmd,... )
{
NSLog (@"Hello");
}
void addMethod ()
{
// create the method
struct objc_method myMethod;
myMethod.method_name = sel_registerName("sayHello");
myMethod.method_imp = sayHello;
// build the method list.
// this memory needs to stick around as long as the
// methods belong to the class.
struct objc_method_list * myMethodList;
myMethodList = malloc (sizeof(struct objc_method_list));
myMethodList->method_count = 1;
myMethodList->method_list[0] = myMethod;
// add method to the class
class_addMethods ( [EmptyClass class], myMethodList );
// try it out
EmptyClass * instance = [[EmptyClass alloc] init];
[instance sayHello];
[instance release];
}
Now let me be clear that I don't think is fun or easy, but it is possible. It also wouldn't be too hard to create some sort of wrapper API to do this. In any case, there are only about seven lines of code that involve actually adding the method.
Also, please don't use the above in the production code. It's a proof of concept only. It uses local variables, imperfect error checking, and so on. If you need real examples, check out the source code for PyObjC.
Add Methods at Runtime in Objective-C
Posted Oct 26, 2006 — 10 comments below
Posted Oct 26, 2006 — 10 comments below
ssp — Oct 26, 06 2163
Chris — Oct 26, 06 2165
- it's very useful for bridging dynamic languages like Ruby and Python to Objective-C. The translation layers for these languages need to add methods on demand.
- it's useful for creating Mock Objects that stand in for other objects in test-driven development methodologies.
Marco — Oct 26, 06 2166
Rob In der Maur — Oct 26, 06 2168
Scott Stevenson — Oct 26, 06 2170
Andy has some examples in his post, but this also shows you how categories can load at runtime.
Jon Hess — Oct 26, 06 2174
This may sound like a hack, but it is a much more appropriate hack than the sledgehammer that was poseAs:. Its also much more appropriate than smashing a method with a category.
Jon Hess — Oct 26, 06 2175
This is much cleaner than poseAs:. For example, if you posed as window, and did something special in sendEvent: (Let's not forget this would be a hack), your sendEvent: might not get invoked for every event for some subclasses of window. If you dynamically created a class, added a sendEvent: method, and swtiched the window you were interested in to be a member of your class, you could intercept *all* of the events heading to that window. This is much more correct, and percise than poseAs:.
Frederik Seiffert — Oct 27, 06 2176
AFAIK, methods like the above have been used in AspectCocoa.
M. Vock — Oct 27, 06 2177
Scott Stevenson — Oct 27, 06 2185
This isn't a complete program. You'd need to add the above code to a Cocoa project.