Controlling the Uncontrollable: A Guide to Method Swizzling in Objective-C
Learn how to use Objective-C's dynamic runtime to modify class behavior at execution time using Method Swizzling, and how to avoid common pitfalls like infinite recursion.
01 Dec 2025, 02:10 UTC

The Problem: Adding Logic to Closed Frameworks
You are tasked with adding a logging event every time a specific screen appears in an iOS application. The logic belongs in viewWillAppear:, but the project has dozens of View Controllers, and you cannot realistically subclass every single one of them to override that method. You need a way to inject behavior into an existing class without modifying its source code or changing the inheritance hierarchy.
The solution is Method Swizzling. This technique leverages the Objective-C runtime to swap the implementation of two methods at execution time, allowing you to "hook" into system behavior and execute your own code before or after the original logic runs.
How the Runtime Handles Messages
To understand swizzling, you must first understand Dynamic Dispatch. In many languages, a method call is linked to a specific memory address at compile time. Objective-C does not do this. Instead, it uses a function called objc_msgSend.
When you call [object doSomething], the runtime looks up the doSomething selector (a unique string identifier) in the class's method list to find the corresponding IMP (Implementation), which is the actual function pointer to the executable code. Swizzling is simply the act of swapping these IMP pointers in the class's dispatch table.
Implementing a Swizzle Safely
The most common way to swizzle is using method_exchangeImplementations from runtime.h. To avoid infinite recursion—where your new method calls the original, which has now been swapped to call your new method—you must call the "swizzled" selector inside your implementation.
Worked Example: Global View Logging
In this example, we inject a log message into UIViewController's viewWillAppear: method. This code should be executed once, typically in a category's +load method, which the runtime calls when the class is first loaded into memory.
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation UIViewController (Logging)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [UIViewController class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(my_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// Swap the implementations
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)my_viewWillAppear:(CGFloat)animated {
// This call actually invokes the ORIGINAL viewWillAppear:
// because the implementations have been swapped.
[self my_viewWillAppear:animated];
NSLog(@"View will appear: %@", [self className]);
}
@end
Execution Details
- Permissions: Requires
#import <objc/runtime.h>. - Placement: Run within
+loadto ensure the swap happens before any instances are created. - Risk: If you call
[self viewWillAppear:]instead of[self my_viewWillAppear:]inside the swizzled method, the app will enter an infinite loop and crash with a stack overflow.
Trade-offs and Critical Limitations
While powerful, swizzling is a "surgical" operation that can leave scars if used carelessly.
| Risk | Impact | Mitigation |
|---|---|---|
| Debugging Complexity | The call stack no longer matches the source code. | Limit swizzling to a few well-documented utility categories. |
| Framework Updates | Apple may change the internal implementation of the method. | Verify behavior after every major OS or SDK update. |
| Thread Safety | Modifying the dispatch table during execution can cause race conditions. | Always perform swizzling inside +load or a dispatch_once block. |
Verification and Rollback
To verify the swizzle worked, you can use the debugger (LLDB) to inspect the method implementation. Run the following command in the console:
p (IMP)class_getMethodImplementation([UIViewController class], @selector(viewWillAppear:))
If the address returned matches the address of your my_viewWillAppear: method, the swap was successful.
Rollback: Because swizzling changes the global state of the class for the duration of the process, you cannot "undo" it for a single instance. To revert the change, you must call method_exchangeImplementations again to swap the pointers back to their original positions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.