Mobile Development 21 min read

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.

GrowingIO Tech Team
GrowingIO Tech Team
GrowingIO Tech Team
Mastering iOS AOP: From Method Swizzling to Fishhook and Beyond

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

+load

and

dispatch_once

:

<code>+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&amp;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

NSLog

or

objc_msgSend

. It works by rebinding the pointer in the

__la_symbol_ptr

section to a custom implementation at runtime. The following diagram illustrates the symbol resolution chain:

Fishhook symbol resolution
Fishhook symbol resolution

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

libffi

for 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.

mobile developmentiosaopruntimeObjective-CMethod Swizzling
GrowingIO Tech Team
Written by

GrowingIO Tech Team

The official technical account of GrowingIO, showcasing our tech innovations, experience summaries, and cutting‑edge black‑tech.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.