Search code examples
objective-cobjective-c-runtimemethod-swizzling

How to correctly swizzle with method_setImplementation()?


I am trying to understand how to correctly swizzle using the method_setImplementation(). So far online I have seen two predominantly popular ways to method swizzle, once is using method_setImplementation() while the other levarages method_exchangeImplementation.

When swizzling using method_exchangeImplementation() you can get into an issue where a target class does not implement the method you are trying to swizzle. To avoid problems here with an ancestor class implementing the method and incorrectly swizzling that, you first add an implementation to your target class and then swap the swizzledMethod IMP with your ancestor classes method to get things in order. This all makes perfect sense to me...

My question is how do we handle the same above case when swizzling with method_setImplementation? Do we not have to handle this case (intuitively I feel like we do) ? If we do need to handle the above case sample code would help me greatly because I am lost as to how to handle it.


Solution

  • When swizzling with free functions (a.k.a regular functions), you need to take into consideration the actual method signature that the Objective-C compiler generates for the method you want swizzled, and the fact that IMP is just a function pointer, declared like this:

    typedef id (*IMP)(id, SEL, ...);
    

    Assuming you have a method named doSomethingWithObject:afterDelay:, which looks like this

    - (void)doSomethingWithObject:(id)object afterDelay:(NSTimeInterval)delay
    

    the corresponding C function that the compiler generates is (see Wikipedia for more about name mangling in Objective-C):

    void _i_MyClass_doSometingWithObject_afterDelay(id self, SEL _cmd, id object, NSTimeInterval delay)
    

    so if you want to exchange it, you need to create a similar method and pass the pointer to it:

    void swizzledDoSometingWithObjectAfterDelay(id self, SEL _cmd, id object, NSTimeInterval delay) {
        // custom implementation
    }
    
    // ....
    method_setImplementation(method, (IMP)swizzledDoSometingWithObjectAfterDelay);
    

    If the method you want to swizzle might be declared in an ancestor class, and you want to swizzle it only for the child class you're interested into, you can use class_replaceMethod(), which either adds or replaces the method, and that only affects the target class:

    SEL selector = @selector(doSomethingWithObject:afterDelay:);
    IMP newImp = (IMP)swizzledDoSometingWithObjectAfterDelay
    Method method = class_getClassMethod([MyClass class], selector);
    const char * encoding = method_getTypeEncoding(method);
    class_replaceMethod([MyClass class], selector, newIMP, encoding)