Home > cocoa > Hijacking with method_exchangeImplementations()

Hijacking with method_exchangeImplementations()

Based on a discussion from StackOverflow.

Sometimes you want to inject some logic into a method you don’t control. The most common sane reason to do this is for debugging or profiling. For instance, you might want to log every time the various NSNotificationCenter methods are called so you can determine if that’s a performance bottleneck. (As I discovered myself, if you have thousands of notification observations in your system, it can be a serious performance problem.)

In most OOP languages your only option would be to subclass the object you want to instrument and then arrange for every instance of that object to be your subclass. In many cases that’s either very difficult or outright impossible, particularly if the object is used internally by a system framework. But Objective-C is highly dynamic, and message dispatching is resolved at runtime. You can modify how it works.

On Mac, there is an NSObject method called poseAsClass: that can achieve this easily. Unfortunately, it’s deprecated in 10.5 and isn’t available at all for Mac 64-bit and iPhone. I want a technique that I can use reliably for all my platforms, and luckily there is a fully supported alternative: method_exchangeImplemenations().

So how do we use it? First, we create a category on our target (NSNotificationCenter):

@interface NSNotificationCenter (RNHijack)
+ (void)hijack;
@end

@implementation NSNotificationCenter (RNHijack)
+ (void)hijackSelector:(SEL)originalSelector withSelector:(SEL)newSelector
{
    Class class = [NSNotificationCenter class];
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method categoryMethod = class_getInstanceMethod(class, newSelector);
    method_exchangeImplementations(originalMethod, categoryMethod);
}

+ (void)hijack
{
    [self hijackSelector:@selector(removeObserver:)
        withSelector:@selector(RNHijack_removeObserver:)];
}

- (void)RNHijack_removeObserver:(id)notificationObserver
{
    NSLog(@"Removing observer: %@", notificationObserver);
    [self RNHijack_removeObserver:notificationObserver];
}

Then, somewhere early in my program, I call [NSNotificationCenter hijack]. Now, every removeObserver: message is logged, no matter who sends it, even inside Cocoa itself. To make sure that I catch everything, I often put this in main.m, before the call to ‘NSApplicationMain()’.

The line to pay particular notice to is the apparent infinite loop in RNHijack_removeObserver: that seems to call itself. Why does this work? Because method_exchangeImplementation() swaps the two implementations. The old removeObserver: now points to my implementation, but conversely RNHijack_removeObserver: points to the old implementation.

It is hard to imagine code that is more confusing and fragile than this, which is why this technique must be used with the utmost care. Personally I can’t imagine using this outside of debugging and profiling. But when you need it, it is an incredibly powerful tool.

Categories: cocoa Tags:
  1. July 7th, 2010 at 18:33 | #1

    One change you could made which would do away with the need to call [NSNotificationCenter hijack] early on in your program is to swizzle removeObserver: in a +load method on your category. This would be called automatically for you.

    • July 7th, 2010 at 21:59 | #2

      The trade-off here is whether you want your test code to be something that magically infects your program just by being compiled, or if you want it to set off the warning flares that a commit into main.m cause vs a more straightforward approach like AppDelegate’s -init. There are pros and cons of +load, but I’ve seen this kind of code accidentally left in products (I just pulled out a leak detector a couple of months ago that no one had realized was in there, shouldn’t have been in there, and was injected in a way similar to this so no one had noticed it). On the other hand, it means you can inject debug magic without making any changes to the target code at all, which is nice, as long as you’re very careful not to commit it.

      My style is often very tempered by working in a large team with a very wide range of skills, so I work very hard to isolate magic into places that I will notice in the subversion commit logs. Those who work on solo projects or with smaller teams of highly skilled developers definitely may benefit from other styles.

      For those reading along at home, +load is basically a super-magical form of +initialize that gets called at load time (rather than at first method send), and is so special-cased in the runtime that multiple categories can implement it and they all will run correctly. It is wonderfully useful when you need it. Thanks for mentioning it.

  1. No trackbacks yet.