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.
Design Element
Add Methods at Runtime in Objective-C
Posted Oct 26, 2006 — 10 comments below




 

ssp — Oct 26, 06 2163

To which extent would doing this actually be useful rather than just geek-cool? Any good examples for that?

Chris — Oct 26, 06 2165

Quickies:

- 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

PyObjC, for example, makes use of libffi

Rob In der Maur — Oct 26, 06 2168

This could be helpful in tweaking 'closed' (that is, not exposed through a public API) functionality; I once fiddled around using this concept to override the Print Book functionality that Apple has put into iPhoto (e.g. similar to what MyPublisher is doing). I know this is tricky, especially with 'closed' API's but sometimes it is the only way to extend or modify the functionality Apple has provided us with...

Scott Stevenson — Oct 26, 06 2170 Scotty the Leopard

To which extent would doing this actually be useful rather than just geek-cool?

Andy has some examples in his post, but this also shows you how categories can load at runtime.

Jon Hess — Oct 26, 06 2174

You could build a class dynamically at runtime, that subclasses some know class. Then add methods to it to override methods from the baseclass, then swtich a sepcific instance of the base class to your new derived class.

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

Err, when I said "some known class" that isn't really what I meant. I meant, if you want to specialize the behaviour of some specific object(s), but not all objects, and you only know their partial type. By using the methods outlined in my comment, and this post, you could dynamically override the method of the unknown object.

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

Adding methods at runtime is also useful for adding Aspect-Oriented Software Development capabilities to Objective-C.
AFAIK, methods like the above have been used in AspectCocoa.

M. Vock — Oct 27, 06 2177

What would be the gcc command on the command line to get NSLog say "Hello"?

Scott Stevenson — Oct 27, 06 2185 Scotty the Leopard

What would be the gcc command on the command line to get NSLog say "Hello"?

This isn't a complete program. You'd need to add the above code to a Cocoa project.




 

Comments Temporarily Disabled

I had to temporarily disable comments due to spam. I'll re-enable them soon.





Copyright © Scott Stevenson 2004-2015