SoFunction
Updated on 2025-04-12

Detailed explanation of iOS Method Swizzling usage traps

When reading the source code of a project of the team, I found that the writing method of Method Swizzling was flawed. This article mainly introduces what the correct way to write iOS Method Swizzling should be.

Here is an implementation of iOS Method Swizzling:

+ (void)load {
  Class class = [self class];

  SEL fromSelector = @selector(func);
  SEL toSelector = @selector(easeapi_func);

  Method fromMethod = class_getInstanceMethod(class, fromSelector);
  Method toMethod = class_getInstanceMethod(class, toSelector);

  method_exchangeImplementations(fromMethod, toMethod);
}

This writing works normally at some point, but in fact there are some problems. So what is the problem?

An example

To illustrate this problem, let's first assume a scenario:

@interface Father: NSObject
-(void)easeapi;
@end
@implementation Father
-(void)easeapi {
  //your code
}
@end

//Son1 inherits from Father@interface Son1: Father
@end
@implementation Son1
@end

//Son2 inherits from Father and HOOK the easeapi method.@interface Son2: Father
@end
@implementation Son2
+ (void)load {
  Class class = [self class];

  SEL fromSelector = @selector(easeapi);
  SEL toSelector = @selector(new_easeapi);

  Method fromMethod = class_getInstanceMethod(class, fromSelector);
  Method toMethod = class_getInstanceMethod(class, toSelector);

  method_exchangeImplementations(fromMethod, toMethod);
}
-(void)new_easeapi {
  [self new_easeapi];
  //your code
}
@end

It seems that there is no problem, and the Son2 method is also exchanged successfully, but when we execute [Son1 easeapi], we find CRASH.

'-[Son1 new_easeapi]: unrecognized selector sent to instance 0x600002d701f0''

This is strange. What we HOOK is Son2's method, how can Son1 crash?

Why does a crash occur

To explain this issue, we still need to return to the principle.

First of all, class_getInstanceMethod will look for the implementation of the parent class.

In the above example, easeapi is implemented in Son2's parent class Father. After executing method_exchangeImplementations, Father's easeapi and Son2's new_easeapi perform method exchange.

After the exchange, when Son1 (a subclass of Father) executes the easeapi method, Father's easeapi method implementation will be found through "message search".

The key point is here!

Since method exchange has occurred, Son2's new_easeapi method is actually executed.

-(void)new_easeapi {
  [self new_easeapi];
  //your code
}

Damn it is executed [self new_easeapi] in new_easeapi. At this time, self here is an instance of Son1, but there is no SEL of new_easeapi in Son1 and its parent class Father. If the corresponding SEL cannot be found, it will naturally CRASH.

Under what circumstances will there be no problem?

As mentioned above, "This writing method can work normally at some point." So, when will there be no problem when directly executing method_exchangeImplementations?

At least there will be no problem in the following scenarios:

There is an implementation of easeapi in Son2

In the above example, if we override the easeapi method in Son2, we execute class_getInstanceMethod(class, fromSelector) to get the easeapi implementation of Son2, not Father's. In this way, after executing method_exchangeImplementations, the implementation of Father will not be affected.

new_easeapi implementation improvement

- (void) new_easeapi {
  //[self new_easeapi];//Block this code  //your code
}

In this scenario, there will be no problem since [self new_easeapi] is not executed. But this will not achieve the effect of HOOK.

Improve optimization

Recommended Method Swizzling implementation:

+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Class class = [self class];

    SEL fromSelector = @selector(easeapi);
    SEL toSelector = @selector(new_easeapi);

    Method fromMethod = class_getInstanceMethod(class, fromSelector);
    Method toMethod = class_getInstanceMethod(class, toSelector);

    if(class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
      class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
    } else {
      method_exchangeImplementations(fromMethod, toMethod);
    }
  });
}

You can see that there are at least two changes:

dispatch_once
Although dyld can ensure that Class's load is thread-safe, it is still recommended to use dispatch_once as protection to prevent repeated exchanges when load is displayed and forced to call in extreme cases (the first exchange is successful, and the next time it is replaced...), causing logical confusion.

Added class_addMethod judgment
class_addMethod & class_replaceMethod
Still understand it from a definition.

class_addMethod
Add a SEL implementation (or a binding between SEL and the specified IMP) to the specified Class. Return YES after adding successfully, and return NO if SEL has existed or failed to add.

It has two points to pay attention to:

  • If the SEL is implemented in the parent class, a method overrides the parent class will be added;
  • If there is already SEL in this Class, NO is returned.

Execution of class_addMethod can avoid interference with the parent class, which is why it is recommended that you try to use class_addMethod first. Obviously, because of the influence of iOS Runtime messaging mechanism, only the method_exchangeImplementations operation may affect the parent class's methods. Based on this principle, if HOOK is the method implemented in this class, it is completely fine to use method_exchangeImplementations directly.

class_replaceMethod

  • If the Class does not have a specified SEL, the function of class_replaceMethod is the same as class_addMethod;
  • If the Class has a specified SEL, the function of class_replaceMethod is the same as method_setImplementation.

This is the end of this article about the detailed explanation of the use traps of iOS Method Swizzling. For more related iOS Method Swizzling content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!