BS
BleepingSwift
Published on

> Ways to Shoot Yourself in the Foot with Method Swizzling

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @0x7fs

Method swizzling grants immense power over Objective-C's runtime. With that power comes numerous opportunities to create bugs that are nearly impossible to track down. Let's explore the most common ways swizzling goes wrong.

Forgetting to Call the Original

The most basic mistake: your swizzled method doesn't call through to the original implementation.

// Broken - never calls original viewDidAppear:
- (void)tracking_viewDidAppear:(BOOL)animated {
    [Analytics trackScreen:self];
    // Oops, forgot to call original
}

Now every view controller in your app is broken. Subclass implementations of viewDidAppear: never run. Layouts might not complete. Data won't load. And the crash will happen somewhere completely unrelated to your swizzling code.

Swizzling in the Wrong Place

Swizzle too late and some code sees the un-swizzled version. Swizzle too early and dependencies might not be ready.

// Risky - may run after some view controllers already loaded
- (void)applicationDidFinishLaunching:(UIApplication *)application {
    [self setupSwizzling]; // Too late!
}

Always swizzle in +load. It runs before main(), guaranteeing consistent behavior from the first method call.

Double Swizzling

If your swizzling code runs twice, the implementations swap back to their original positions:

// Dangerous if called multiple times
- (void)setupSwizzling {
    Method original = class_getInstanceMethod([UIView class], @selector(layoutSubviews));
    Method swizzled = class_getInstanceMethod([UIView class], @selector(my_layoutSubviews));
    method_exchangeImplementations(original, swizzled);
}

Call this twice and you've un-swizzled yourself. Always use dispatch_once:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // Swizzle here
    });
}

Swizzling Without Checking Inheritance

Consider a class hierarchy where only the superclass implements a method. If you swizzle on the subclass, you're actually swizzling the superclass's method:

// ParentView implements -layoutSubviews
// ChildView does not override it

// This swizzles ParentView's implementation, affecting ALL ParentView subclasses
Method original = class_getInstanceMethod([ChildView class], @selector(layoutSubviews));

The safer pattern checks whether the method exists on the class itself:

SEL originalSel = @selector(viewDidAppear:);
SEL swizzledSel = @selector(tracking_viewDidAppear:);

Method originalMethod = class_getInstanceMethod(class, originalSel);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSel);

BOOL didAdd = class_addMethod(class,
    originalSel,
    method_getImplementation(swizzledMethod),
    method_getTypeEncoding(swizzledMethod));

if (didAdd) {
    class_replaceMethod(class,
        swizzledSel,
        method_getImplementation(originalMethod),
        method_getTypeEncoding(originalMethod));
} else {
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

Breaking Method Signatures

If your swizzled method has a different signature than the original, chaos ensues:

// Original takes (BOOL)animated
- (void)viewDidAppear:(BOOL)animated;

// Swizzled takes no arguments - WRONG
- (void)tracking_viewDidAppear {
    [self tracking_viewDidAppear]; // Missing argument!
}

The compiler won't catch this because selector lookup is dynamic. At runtime, arguments get misaligned on the stack. You might get garbage values, crashes, or mysteriously wrong behavior.

Naming Collisions

What if two libraries both try to swizzle the same method with the same swizzled selector?

// Analytics SDK
- (void)analytics_viewDidAppear:(BOOL)animated { ... }

// Crash reporting SDK
- (void)analytics_viewDidAppear:(BOOL)animated { ... } // Collision!

One category replaces the other's method. Use distinctive prefixes that won't collide. Your company name or a UUID prefix works well.

Order Dependencies

Multiple swizzles on the same method create a chain. The order they execute matters:

// Library A swizzles in +load
// Library B swizzles in +load

// Final call chain: B's swizzle -> A's swizzle -> original

But +load order depends on link order and framework dependencies. Change your Podfile or add a new dependency, and suddenly the order changes. Behavior shifts mysteriously.

Threading Issues

If you swizzle while another thread is calling the method, you can hit a race condition:

// Thread 1: Executing viewDidAppear:
// Thread 2: Swizzling viewDidAppear: right now

// Thread 1 might call half-old, half-new implementation

This is why +load is important—it runs before any other code can use the methods.

Debugging Nightmares

When something goes wrong with swizzled code, debugging is painful. The stack trace shows your method name, but the implementation is different. You set a breakpoint in viewDidAppear:, but the swizzled code doesn't hit it.

The debugger's po command shows the original selector name, not the actual implementation running. Stepping through code jumps unexpectedly between implementations.

Add comments aggressively and consider logging:

- (void)tracking_viewDidAppear:(BOOL)animated {
    #if DEBUG
    NSLog(@"[SWIZZLE] tracking_viewDidAppear called on %@", self);
    #endif
    [self tracking_viewDidAppear:animated];
}

App Store Rejection Risks

While swizzling isn't explicitly prohibited, Apple has rejected apps for swizzling certain system methods. Swizzling private API methods is especially risky. If you're swizzling to work around App Store review or circumvent platform security, expect rejection.

When to Just Say No

If you can solve the problem another way, do that instead. Swizzling should be a last resort for:

  • Adding behavior to closed-source code you can't modify
  • Patching urgent bugs while waiting for a proper fix
  • System-wide instrumentation during development

For code you control, prefer protocols, subclassing, delegation, and composition. They're predictable, debuggable, and won't mysteriously break when iOS updates.

Swizzling is a sharp tool. Respect its edges.

subscribe.sh

// Stay Updated

Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.

>

By subscribing, you agree to our Privacy Policy.