mick@next:~/objc/power-of-swizzling$/* Rediscovering the language that built the future */
The Power of Method Swizzling in Objective-C
objc / power-of-swizzling.m

The Power of Method Swizzling in Objective-C

Mick MacCallumMick MacCallum
@Objective-C@Runtime@Advanced

Objective-C's runtime is remarkably dynamic. Classes are objects, methods are just function pointers in a lookup table, and you can modify that table while the app runs. Method swizzling exploits this dynamism to replace or wrap method implementations without subclassing.

How Methods Work

Every Objective-C method has two key components: a selector (the name) and an implementation (the code). The runtime maintains a dispatch table mapping selectors to implementations. When you send a message, the runtime looks up the selector and calls the corresponding function.

// These are equivalent:
[object doSomething];
objc_msgSend(object, @selector(doSomething));

Swizzling changes what implementation a selector points to. Call the same method, run different code.

The Basic Technique

The standard swizzling pattern uses method_exchangeImplementations:

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewDidAppear:);
        SEL swizzledSelector = @selector(tracking_viewDidAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)tracking_viewDidAppear:(BOOL)animated {
    // This actually calls the original implementation now
    [self tracking_viewDidAppear:animated];

    // Our added behavior
    NSLog(@"Screen appeared: %@", NSStringFromClass([self class]));
}

@end

After swizzling, calling viewDidAppear: executes tracking_viewDidAppear:, and calling tracking_viewDidAppear: executes the original viewDidAppear: code. They've traded places.

Why +load and dispatch_once?

The +load method runs exactly once when the class is added to the runtime, before main() executes. This guarantees swizzling happens early enough that no code sees the un-swizzled version.

The dispatch_once block prevents accidental double-swizzling. Swizzling twice would swap the implementations back to their original positions—not what you want.

Real-World Use Cases

Analytics and logging are the classic examples. You can track every view controller appearance, button tap, or network request without modifying each class individually.

@implementation UIButton (Analytics)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method original = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method swizzled = class_getInstanceMethod(self, @selector(analytics_sendAction:to:forEvent:));
        method_exchangeImplementations(original, swizzled);
    });
}

- (void)analytics_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    [Analytics trackButtonTap:self];
    [self analytics_sendAction:action to:target forEvent:event];
}

@end

Bug fixes for system frameworks can be applied this way. If you discover a bug in Apple's code that crashes your app, swizzling lets you patch around it while waiting for an OS update.

Testing benefits from swizzling to mock system behavior. Need to test code that uses NSDate? Swizzle it to return a fixed timestamp.

static NSDate *mockedDate = nil;

@implementation NSDate (Testing)

+ (void)load {
    Method original = class_getClassMethod(self, @selector(date));
    Method mock = class_getClassMethod(self, @selector(mockDate));
    method_exchangeImplementations(original, mock);
}

+ (instancetype)mockDate {
    return mockedDate ?: [self mockDate]; // Calls original
}

+ (void)setMockedDate:(NSDate *)date {
    mockedDate = date;
}

@end

Swizzling Class Methods

The technique differs slightly for class methods. Use class_getClassMethod or work with the metaclass:

Method originalMethod = class_getClassMethod([NSDate class], @selector(date));

A Safer Pattern

The exchange approach has edge cases around inheritance. Here's a more robust version:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewDidAppear:);
        SEL swizzledSelector = @selector(tracking_viewDidAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class,
            originalSelector,
            method_getImplementation(swizzledMethod),
            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

This handles the case where a class doesn't implement a method directly but inherits it.

When Swizzling Makes Sense

Swizzling works best when you need to add behavior to code you don't control. Apple's frameworks, third-party SDKs, code spread across too many places to modify individually—these are swizzling's sweet spot.

When you control the code, prefer normal techniques: subclassing, delegation, composition. They're easier to understand, debug, and maintain.

Swizzling is powerful precisely because it breaks normal expectations. A method can do something completely different from what its name and documentation suggest. Use that power responsibly. Your future self—and your teammates—will thank you.