Replacing Objective-C Methods at Runtime
One of the more interesting features of Objective-C is the ability to replace entire methods at runtime, and it turns out that it's actually pretty easy. To do this, we need to dig into libobjc, which has headers in /usr/include/objc.In this case, we specifically want to look at objc-class.h, which is where the objc_method struct is defined:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
This is the template for all Objective-C methods. This may look pretty alien, but there's not much to it. The first field is the selector, which is basically a method name. The second field is a C string which stores type encodings (don't worry about it for now).
The last field is the IMP, or implementation. An IMP is a pointer to the actual function that is called when this Objective-C method is activated. This field is what we'll use to replace the method. First, here's our extremely simple class, with one method:
@interface MiniObject : NSObject { }
- (void)generateFrogs;
@end
@implementation MiniObject
- (void)generateFrogs
{
NSLog(@"Generating Frogs...");
}
@end
So if this class has its way, we'll be generating frogs all day and night. As omnipotent programmers with a dynamic runtime, though, we have other plans. Here's our Controller class. Remember to import objc-class.h!
#import "Controller.h"
#import <objc/objc-class.h>
@implementation Controller
- (IBAction)click:(id)sender
{
MiniObject * mini = [[MiniObject alloc] init];
[mini generateFrogs];
SEL frogSel = @selector(generateFrogs);
Method frogMethod = class_getInstanceMethod([MiniObject class], frogSel);
SEL lizSel = @selector(generateLizards);
Method lizardMethod = class_getInstanceMethod([self class], lizSel);
frogMethod->method_imp = lizardMethod->method_imp;
[mini generateFrogs];
[mini release];
}
- (void) generateLizards
{
NSLog(@"Generating Lizards...");
}
@end
So the class has two methods, a "click" action method and generateLizards. First, we create a MiniObject and call generateFrogs (the original method):
MiniObject * mini = [[MiniObject alloc] init];
[mini generateFrogs];
Next, we get the Method structs for both generateFrogs and generateLizards (Method is a pointer to an objc_method struct):
SEL frogSel = @selector(generateFrogs);
Method frogMethod = class_getInstanceMethod([MiniObject class], frogSel);
SEL lizSel = @selector(generateLizards);
Method lizardMethod = class_getInstanceMethod([self class], lizSel);
Notice how we use [self class] in the last line because we want to replace the original method in MiniObject with the one from Controller.
Finally, we use standard C syntax to replace the value of the method_imp field in frogMethod with the method_imp from lizardMethod. Then, we send the generateFrogs message again:
frogMethod->method_imp = lizardMethod->method_imp;
[mini generateFrogs];
The run log looks like this:
Generating Frogs...
Generating Lizards...
The code shows that we never actually called generateLizards directly. Instead, we replaced the implementation of generateFrogs with that of generateLizards. Pretty fancy.
Replacing Objective-C Methods at Runtime
Posted May 15, 2006 — 13 comments below
Posted May 15, 2006 — 13 comments below
Abhi Beckert — May 15, 06 1234
Uli Kusterer — May 15, 06 1235
Due to that, I personally prefer poseAsClass: because it allows you to simply override an existing method, but at the same time reroutes all future requests for the base class to your subclass. Of course, poseAsClass: doesn't retroactively change objects created before the posing happens, but I rarely need that.
Uli Kusterer — May 15, 06 1236
It's also handy when you're doing code insertion in another application, e.g. using mach_*, APE or a plugin.
Scott Stevenson — May 15, 06 1237
The point of this series has been more to expose the inner workings of Objective-C and what sort of things define a dynamic language.
That said, there are applications, particularly when you want to change the behavior of built-in class. All of these sort of tricks are not that useful for a notebook or drawing application, but might be a huge help when building a language bridge.
Joshua — May 15, 06 1238
Uli Kusterer — May 18, 06 1272
It's also handy when you're doing code insertion in another application, e.g. using mach_*, APE or a plugin.
Max Groe — Jun 26, 06 1381
Rog — Oct 10, 06 2017
One possible meaning could be to transform mutable objects to immutable.
@implementation MyMutableObject
- (id)transition {
if ([self count] > 1) return [self copy];
// change all mutable methods to immutable methods
// ...
// Return the transited object
return (MyObject *)self;
}
@end
DrSet — Jun 21, 07 4433
http://objc.kiev.ua Thank you!
Who want talk with me. Sent me e-mail or you can find me in ICQ 146687.
Matt Gallagher — Mar 30, 08 5697
In Objective-C 2.0 (Mac OS X 10.5 Leopard and later) the approved way to do this uses the method_exchangeImplementations function instead. As in:
Method frogs = class_getInstanceMethod([MiniObject class], @selector(generateFrogs)); Method lizards = class_getInstanceMethod([Controller class], @selector(generateLizards)); method_exchangeImplementations(lizards, frogs);
This approach is safer and atomic.
For those asking about applications of this approach: it is mostly used for debugging. If you replace methods like -[NSObject dealloc] but still invoke the default from your replacement, then you can perform all kinds of memory logging/debugging, etc.
Juanita — Apr 09, 08 5710
Bryan Elliott — May 04, 09 6729
+(NSArray*) listInstanceMethods { NSUInteger outCount; NSUInteger i; // struct objc_method * Method* list = class_copyMethodList([self class], &outCount); Method meth; NSMutableArray* ret = [[NSMutableArray alloc] initWithCapacity:outCount]; /* for each item in list, copy out the text selector */ for (i=0; imethod_name)];
}
free(list);
return [NSArray arrayWithArray:ret];
}
I get a warning any time I try to directly access properties of Methods. I ask you, if method_name is deprecated, what IS the approved way of getting the selector for a given Method struct?
Bryan — May 04, 09 6730
[ret addObject:NSStringFromSelector(meth->method_name)];