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 subclass
subFunctionA
Method of substitutionfunctionA
Method oldM
Is a method obtained from the parent class(SEL: functionA, IMP: functionA)
Method newM
The method is obtained from the subclass itself(SEL: subFunctionA, IMP: subFunctionA)
- Can you successfully add methods:
(SEL: functionA, IMP: subFunctionA)
- No: The system already exists
SEL: functionA
The method of - Yes: It does not exist
SEL: functionA
The method of- The subclass
(SEL: subFunctionA, IMP: subFunctionA)
Replace with(SEL: subFunctionA, IMP: functionA)
- The subclass
- No: The system already exists
- 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