Mastering iOS AOP: From Method Swizzling to Fishhook and Beyond
This article explains the fundamentals of Aspect‑Oriented Programming on iOS, compares major AOP techniques such as Method Swizzle, Aspects, MPSwizzler, ISA‑swizzle KVO, Fishhook, Thunk and Clang instrumentation, and provides practical code examples and best‑practice guidelines.
AOP Concept
Aspect‑Oriented Programming (AOP) enables adding cross‑cutting behavior to existing code without modifying the original source, typically by intercepting method execution before or after the original implementation.
Main iOS AOP Solutions
Method Swizzle
Method Swizzling leverages the dynamic nature of Objective‑C to exchange the implementations of two selectors at runtime using
method_exchangeImplementations. A typical implementation swaps a custom selector with the original one inside
+loadand
dispatch_once:
<code>+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL originalSelector = @selector(method_original:);
SEL swizzledSelector = @selector(method_swizzle:);
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
BOOL didAddMethod = class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(aClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
</code>Common pitfalls include non‑atomic swaps, unintended changes to third‑party code, naming conflicts, altered method arguments, order‑dependent swizzles, recursion‑like behavior, and debugging difficulty.
Aspects
Aspects is a lightweight iOS AOP library that uses Method Swizzling to hook class or instance methods. It provides
aspect_hookSelector:withOptions:usingBlock:error:for both class‑level and instance‑level hooks, allowing execution before, after, or instead of the original method. However, it incurs noticeable overhead and is not recommended for production code.
MPSwizzler
MPSwizzler, used in the MixPanel SDK, extends Method Swizzling with runtime‑cancelable hooks and block‑based implementations to avoid naming collisions. Example implementation:
<code>+ (void)swizzleSelector:(SEL)aSelector onClass:(Class)aClass withBlock:(swizzleBlock)aBlock named:(NSString *)aName {
Method aMethod = class_getInstanceMethod(aClass, aSelector);
if (aMethod) {
uint numArgs = method_getNumberOfArguments(aMethod);
if (numArgs >= MIN_ARGS && numArgs <= MAX_ARGS) {
BOOL isLocal = [self isLocallyDefinedMethod:aMethod onClass:aClass];
IMP swizzledMethod = (IMP)mp_swizzledMethods[numArgs - 2];
MPSwizzle *swizzle = [self swizzleForMethod:aMethod];
if (isLocal) {
if (!swizzle) {
IMP originalMethod = method_getImplementation(aMethod);
method_setImplementation(aMethod, swizzledMethod);
swizzle = [[MPSwizzle alloc] initWithBlock:aBlock named:aName forClass:aClass selector:aSelector originalMethod:originalMethod withNumArgs:numArgs];
[self setSwizzle:swizzle forMethod:aMethod];
} else {
[swizzle.blocks setObject:aBlock forKey:aName];
}
} else {
// Add a new local method for the class
if (!class_addMethod(aClass, aSelector, swizzledMethod, method_getTypeEncoding(aMethod))) {
NSAssert(NO, @"Could not add swizzled method");
return;
}
Method newMethod = class_getInstanceMethod(aClass, aSelector);
MPSwizzle *newSwizzle = [[MPSwizzle alloc] initWithBlock:aBlock named:aName forClass:aClass selector:aSelector originalMethod:originalMethod withNumArgs:numArgs];
[self setSwizzle:newSwizzle forMethod:newMethod];
}
} else {
NSAssert(NO, @"Cannot swizzle method with %d args", numArgs);
}
} else {
NSAssert(NO, @"Cannot find method for %@ on %@", NSStringFromSelector(aSelector), NSStringFromClass(aClass));
}
}
</code>The swizzled implementation calls the original method first, then iterates over stored blocks to execute additional logic.
ISA‑Swizzle KVO
This technique creates a dynamic subclass at runtime (similar to KVO) and overrides selected methods, forwarding calls to the original implementation before executing custom code. Example:
<code>static void growing_viewDidAppear(UIViewController *kvo_self, SEL _sel, BOOL animated) {
Class kvo_cls = object_getClass(kvo_self);
Class origin_cls = class_getSuperclass(kvo_cls);
IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel));
void (*origin_method)(UIViewController *, SEL, BOOL) = (void (*)(UIViewController *, SEL, BOOL))origin_imp;
origin_method(kvo_self, _sel, animated);
// Custom behavior here
}
- (void)createKVOClass {
[self addObserver:[GrowingKVOObserver shared] forKeyPath:kooUniqueKeyPath options:NSKeyValueObservingOptionNew context:nil];
Class kvoCls = object_getClass(self);
Class originCls = class_getSuperclass(kvoCls);
const char *encoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(viewDidAppear:)));
class_addMethod(kvoCls, @selector(viewDidAppear:), (IMP)growing_viewDidAppear, encoding);
}
</code>This approach minimizes intrusion because the original class’s method table remains unchanged.
Fishhook
Fishhook hooks C functions in the Mach‑O lazy symbol pointer table, allowing replacement of system symbols such as
NSLogor
objc_msgSend. It works by rebinding the pointer in the
__la_symbol_ptrsection to a custom implementation at runtime. The following diagram illustrates the symbol resolution chain:
Fishhook is widely used in jailbreak tools and performance analysis to intercept static C functions, complementing Objective‑C runtime‑based AOP.
Thunk Technique
Thunk (or trampoline) code inserts a small stub before or after a target function, preserving the original stack layout while providing a hook point. It can be generated at compile‑time or constructed dynamically at runtime, often relying on
libffifor calling‑convention handling.
Clang Instrumentation
Clang offers built‑in instrumentation passes for code coverage and static analysis. By writing custom LLVM passes, developers can inject AOP‑style hooks during compilation, useful for testing, linting, or coverage collection without modifying source code.
Summary
The article surveys the major iOS AOP approaches—compile‑time, link‑time, and runtime—showing how each fits different scenarios, from lightweight logging to deep system‑level hooking. Understanding these techniques deepens knowledge of Objective‑C’s dynamic runtime and the underlying Mach‑O linking process, enabling developers to choose the most appropriate AOP strategy for their projects.
GrowingIO Tech Team
The official technical account of GrowingIO, showcasing our tech innovations, experience summaries, and cutting‑edge black‑tech.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.