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.
Design Element
Replacing Objective-C Methods at Runtime
Posted May 15, 2006 — 13 comments below




 

Abhi Beckert — May 15, 06 1234

Can anyone actually come up with a real world use for this?

Uli Kusterer — May 15, 06 1235

One gotcha: This only works if the class you're working on actually implements that method itself. If it inherits the method from some superclass, this won't work as expected.

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

@Abhi: Real-world uses: You can essentially use this to write a category that does an override and thus install a workaround to a bug in an OS API (e.g. if you know 10.1 and earlier leak an object in a particular mathod, you can install an override like this on those OS versions to autorelease the leaked object).

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 Scotty the Leopard

Can anyone actually come up with a real world use for this?

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

I love Objective-C! Even without Cocoa, it's still a great language. It's really a pity that GNUstep sucks. Once I get my MacBook Pro (my first Mac) I'll be able to use Cocoa!

Uli Kusterer — May 18, 06 1272

@Abhi: Real-world uses: You can essentially use this to write a category that does an override and thus install a workaround to a bug in an OS API (e.g. if you know 10.1 and earlier leak an object in a particular mathod, you can install an override like this on those OS versions to autorelease the leaked object).

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

I guess this is also just great for some code obfuscation.

Rog — Oct 10, 06 2017

Can anyone actually come up with a real world use for this?
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

I'm programming on Objective-C one year, this is very flaxible language. I open my site about object-orientet programming on Cocoa. This is russian resource. I think it was helping russian and ukrainian programmers.
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

Note about this approach (often called "method swizzling") in Objective-C 2.0:

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

What can I do if my methods required "super" instead of "self" like [super close]; cuz I can't find any workaround to it?

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

Huh. Stripped out my code. The important bit that throws the deprecation warning is:
[ret addObject:NSStringFromSelector(meth->method_name)];




 

Comments Temporarily Disabled

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





Copyright © Scott Stevenson 2004-2015