Objective-C Language Augmenting methods using Method Swizzling


Example

The Objective-C runtime allows you to change the implementation of a method at runtime. This is called method swizzling and is often used to exchange the implementations of two methods. For example, if the methods foo and bar are exchanged, sending the message foo will now execute the implementation of bar and vice versa.

This technique can be used to augment or "patch" existing methods which you cannot edit directly, such as methods of system-provided classes.

In the following example, the -[NSUserDefaults synchronize] method is augmented to print the execution time of the original implementation.

IMPORTANT: Many people try to do swizzling using method_exchangeImplementations. However, this approach is dangerous if you need to call the method you're replacing, because you'll be calling it using a different selector than it is expecting to receive. As a result, your code can break in strange and unexpected ways—particularly if multiple parties swizzle an object in this way. Instead, you should always do swizzling using setImplementation in conjunction with a C function, allowing you to call the method with the original selector.

#import "NSUserDefaults+Timing.h"
#import <objc/runtime.h> // Needed for method swizzling

static IMP old_synchronize = NULL;

static void new_synchronize(id self, SEL _cmd);

@implementation NSUserDefaults(Timing)

+ (void)load
{
    Method originalMethod = class_getInstanceMethod([self class], @selector(synchronize:));
    IMP swizzleImp = (IMP)new_synchronize;
    old_synchronize = method_setImplementation(originalMethod, swizzleImp);
}
@end

static void new_synchronize(id self, SEL _cmd);
{
    NSDate *started;
    BOOL returnValue;

    started = [NSDate date];

    // Call the original implementation, passing the same parameters
    // that this function was called with, including the selector.
    returnValue = old_synchronize(self, _cmd);


    NSLog(@"Writing user defaults took %f seconds.", [[NSDate date] timeIntervalSinceDate:started]);

    return returnValue;
}

@end

If you need to swizzle a method that takes parameters, you just add them as additional parameters to the function. For example:

static IMP old_viewWillAppear_animated = NULL;
static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated);

...

Method originalMethod = class_getClassMethod([UIViewController class], @selector(viewWillAppear:));
IMP swizzleImp = (IMP)new_viewWillAppear_animated;
old_viewWillAppear_animated = method_setImplementation(originalMethod, swizzleImp);

...

static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated)
{
    ...

    old_viewWillAppear_animated(self, _cmd, animated);

    ...
}