- Published on
> The Power of Method Swizzling in Objective-C
- Authors

- Name
- Mick MacCallum
- @0x7fs
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.
// Continue_Learning
Key-Value Observing Deep Dive
KVO lets you observe property changes without delegation or notifications. Here's how it works under the hood and how to use it correctly.
Message Passing - The Heart of Objective-C
Unlike C++ virtual methods, Objective-C uses dynamic message passing. Understanding this mechanism unlocks the language's full power.
Ways to Shoot Yourself in the Foot with Method Swizzling
Swizzling is powerful but dangerous. Here are the common pitfalls and how to avoid turning your clever hack into a debugging nightmare.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.