This is the 8th day of my participation in the August More Text Challenge. For details, see:August is more challenging

Hi 👋

  • Wechat: RyukieW
  • 📦 Technical article archive
  • 🐙 making
My personal project Mine Elic endless sky ladder Dream of books
type The game financial
AppStore Elic Umemi

preface

MethodSwizzing method swapping is more commonly known as dark magic. However, just like the unique martial arts in wuxia novels, there are also cases where the improper use of martial arts can injure 1000 enemies and 800 self.

Here’s a look at some of the pitfalls to avoid getting caught up in.

A, MethodSwizzingTool

For convenience, let’s encapsulate the normal method exchange logic

@implementation MethodSwizzingTool

+ (void)swizzingClass:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel {
    if(! cls) {return; }
    Method oldM = class_getInstanceMethod(cls, oldSel);
    Method newM = class_getInstanceMethod(cls, newSel);
    method_exchangeImplementations(oldM, newM);
}

@end
Copy the code

1.1 Verifying the Validity

@implementation RYModel+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [MethodSwizzingTool swizzingClass:self oldSEL:@selector(functionA) toNewSel:@selector(functionB)];
    });
}

- (void)functionA {
    NSLog(@"%s", __func__);
}

- (void)functionB {
    NSLog(@"%s", __func__);
    [self functionB];
}

@end
Copy the code

Call:

RYModel *ry = [[RYModel alloc] init];
[ry functionA];
Copy the code

Output:

2021-08-07 17:01:53.954201+0800 MethodSwizzing[72309:15057493] -[RYModel functionB]
2021-08-07 17:01:53.954534+0800 MethodSwizzing[72309:15057493] -[RYModel functionA]
Copy the code

Well, it feels encapsulated.

Two, the pit of subclass

2.1 What happens if we replace a superclass method with a self-class method?

Think about:

In subclass RYSubModel, replace the superclass functionA with subclass subFunctionA. What happens if the subclass instance and the superclass instance call functionA separately? (No swapping in parent class)

The subclass code is as follows:

@implementation RYSubModel

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [MethodSwizzingTool swizzingClass:self oldSEL:@selector(functionA) toNewSel:@selector(subFunctionA)];
    });
}

- (void)subFunctionA {
    NSLog(@"%s", __func__);
}

@end
Copy the code

Call:

RYModel *ry = [[RYModel alloc] init];
[ry functionA];

RYSubModel *sub = [[RYSubModel alloc] init];
[sub functionA];
Copy the code

Output:

2021-08-07 17:10:37.990097+0800 MethodSwizzing[72705.15063434] -[RYSubModel subFunctionA] 2021-08-07 17:10:37. 990530 + 0800 MethodSwizzing [72705-15063434] - [RYSubModel subFunctionA]Copy the code

Analysis of the

The method functionA in the parent class is replaced by the method subFunctionA in the subclass. The implementation of functionA becomes subFunctionA.

2.2 Calling the original implementation

When we swap methods and want to continue calling the original implementation, we usually call ourselves like we did with functionB.

So if we want to continue calling functionA after replacing the subclass method with the subclass method, we should write:

- (void)subFunctionA {
    [self subFunctionA];
    NSLog(@"%s", __func__);
}
Copy the code

Call:

RYModel *ry = [[RYModel alloc] init];
[ry functionA];

RYSubModel *sub = [[RYSubModel alloc] init];
[sub functionA];
Copy the code

Output:

Why is that? I can’t find it

Analysis of the

After we replace the method functionA in the superclass with the method subFunctionA in the subclass

In the parent class:

FunctionA call subFunctionA

But the superclass itself does not have subFunctionA in the method list, so the superclass is reporting an unrecognized Selector error.

Modify the call

If we only call subclasses:

RYSubModel *sub = [[RYSubModel alloc] init];
[sub functionA];
Copy the code

Output:

2021-08-07 17:33:28.874108+0800 MethodSwizzing[73690:15075457] -[RYModel functionA]
2021-08-07 17:33:28.874564+0800 MethodSwizzing[73690:15075457] -[RYSubModel subFunctionA]
Copy the code

This is normal

Optimizing MethodSwizzingTool

Can we optimize the MethodSwizzingTool to prevent problems like this?

You can!

3.1 Optimization Ideas

  • The reason why the above method cannot be found is:The subclass directly replaces the methods of the superclass with its own implementation
  • So can we dynamically add a method to the subclass that is the same as the superclass? When you make a substitution in a subclass it doesn’t affect the superclass

3.2 Write optimized code

+ (void)swizzingClassB:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel {
    if(! cls) {return; }
    Method oldM = class_getInstanceMethod(cls, oldSel);
    Method newM = class_getInstanceMethod(cls, newSel);
    
    // Try adding methods to CLS (SEL: oldSel IMP: newM) to prevent subclasses from directly replacing methods in their parent classes
    BOOL addSuccess = class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(oldM));
    
    if (addSuccess) { // Success adds an oldsel-newm method to subclasses without oldSel
        // Replace imp of newSel with IMP of oldM
        class_replaceMethod(cls, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
    }
    else{ method_exchangeImplementations(oldM, newM); }}Copy the code

3.3 Optimize code flow analysis

This case example class call flow analysis:

  • Call a method, ready to use a method in a subclasssubFunctionAMethod of substitutionfunctionA
  • Method oldMIs a method obtained from the parent class(SEL: functionA, IMP: functionA)
  • Method newMThe method is obtained from the subclass itself(SEL: subFunctionA, IMP: subFunctionA)
  • Can you successfully add methods:(SEL: functionA, IMP: subFunctionA)
    • No: The system already existsSEL: functionAThe method of
    • Yes: It does not existSEL: functionAThe method of
      • The subclass(SEL: subFunctionA, IMP: subFunctionA)Replace with(SEL: subFunctionA, IMP: functionA)

  • The parent class instance
    • [ry functionA]
    • Calls are not affected by subclass method swapping
  • The subclass instance
    • [sub functionA]
    • There is no case where the superclass call cannot find the method

What if functionA of the superclass is not implemented?

4.1 analysis

Since there is no implementation of functionA, the alternative approach here does not succeed. The call to subFunctionA loops directly through the loop.

4.2 to optimize

You can avoid infinite loops by setting the default implementation, and you can handle similar situations where the new implementation is also empty, which will not be covered here.

+ (void)swizzingClassB:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel {
    if(! cls) {return; }
    Method oldM = class_getInstanceMethod(cls, oldSel);
    Method newM = class_getInstanceMethod(cls, newSel);
    
    if(! oldM) {// Start with the new implementation, and temporarily add one (ignore the case that the new implementation is also empty, you can handle it similarly)
        class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(newM));
        // Reassign the oldM variable
        oldM = class_getInstanceMethod(cls, oldSel);
        // Create a default implementation that can do something like log collection
        IMP defaultIMP = imp_implementationWithBlock(^(id self, SEL _cmd){
            NSLog(@" Some treatment");
        });
        // Set IMP for oldM
        method_setImplementation(oldM, defaultIMP);
    }
    
    // Try adding methods to CLS (SEL: oldSel IMP: newM) to prevent subclasses from directly replacing methods in their parent classes
    BOOL addSuccess = class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(oldM));
    
    if (addSuccess) { // Success adds an oldsel-newm method to subclasses without oldSel
        // Replace imp of newSel with IMP of oldM
        class_replaceMethod(cls, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
    }
    else{ method_exchangeImplementations(oldM, newM); }}Copy the code

conclusion

Dark magic is good, but it must be used with great care!

In particular, to the essence of the method and method call process in mind can follow one’s wishes, internal work home, advanced martial arts will not hurt themselves.

👋 Welcome to 👋

Read more:

  • Debug interpretation of objc_msgSend source code
  • IOS underlying knowledge comb