IOS underlying principles + reverse article summary
What is method-swizzling?
-
Method-swizzling means method swapping. Its main function is to replace one method implementation with another method implementation at runtime. This is often referred to as the dark magic of iOS.
-
AOP(Aspect Oriented Programming) is a kind of Programming idea, which is different from OOP (Object Oriented Programming).
- Both OOP and AOP are programming ideas
OOP
The idea of programming is moreTends to encapsulate business modules
, dividing more clear logical units;- while
AOP
isExtract and encapsulate sections to extract the common parts of each module, improve module reuse rate, and reduce the coupling between services
.
-
Each class maintains a list of methods, called methodList. In methodList, there are different methods, called methods. Each Method contains sel and IMP
The following figure shows the relationship between sel and IMP before and after switching
Method-swizzling involves related apis
-
Obtain methods from sel
-
Class_getInstanceMethod: method for obtaining an instance
-
Class_getClassMethod: Obtains a class method
-
-
Method_getImplementation: Gets the implementation of a method
-
Method_setImplementation: Sets the implementation of a method
-
Method_getTypeEncoding: Gets the encoding type implemented by the method
-
Class_addMethod: Adds a method implementation
-
Class_replaceMethod: Replaces the implementation of one method with the implementation of another, i.e. aIMP points to bIMP, but bIMP does not necessarily point to aIMP
-
Method_exchangeImplementations: Exchange implementations of two methods, aIMP -> bIMP, bIMP -> aIMP
Pit point 1: A one-time problem during the use of method-Swizzling
The so-called one-time is: Mehod -swizzling is written in the load method, and the load method will actively call many times, which will lead to repeated exchange methods, so that the method sel pointing back to the original IMP problem
The solution
The singleton design principle allows the method exchange to be executed only once, which can be implemented in OC with dispatch_once
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_bestMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
Copy the code
Pit point 2: The subclass is not implemented, the superclass is
In this code, LGPerson implements personInstanceMethod, and LGStudent inherits from LGPerson without implementing personInstanceMethod. What happens when I run this code?
//*********LGPerson class ********* @interface LGPerson: NSObject - (void)personInstanceMethod; @end @implementation LGPerson - (void)personInstanceMethod{NSLog(@"person object method :%s",__func__); } @ the end / / * * * * * * * * * LGStudent class * * * * * * * * * @ interface LGStudent: LGPerson - (void) helloword; + (void)sayHello; @end @implementation LGStudent @end //********* call ********* - (void)viewDidLoad {[super viewDidLoad]; LGStudent *s = [[LGStudent alloc] init]; LGStudent *s = [[LGStudent alloc] init]; [s personInstanceMethod]; LGPerson *p = [[LGPerson alloc] init]; [p personInstanceMethod]; }Copy the code
Where, the method exchange code is as follows, which is implemented by the classification LG of LGStudent
@implementation LGStudent (LG) + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)]; }); // add personInstanceMethod to your personInstanceMethod method // imp - (void)lg_studentInstanceMethod{ Does //// produce recursion? Lg_studentInstanceMethod = personInstanceMethod; lg_studentInstanceMethod = personInstanceMethod; NSLog(@"LGStudent class added lg object method :%s",__func__); } @endCopy the code
Here is the packaged method-Swizzling method
@implementation LGRuntimeTool + (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" the exchange class passed in cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); method_exchangeImplementations(oriMethod, swiMethod); }Copy the code
By debugging the actual code, it is found that it will be called in PpersonInstanceMethod
Method, which is explained in more detail below
-
[s personInstanceMethod]; Lg_studentInstanceMethod = lg_studentInstanceMethod = LGStudent
-
The breakdown point is [p personInstanceMethod]; , the essential reasons are: LGStudent’s class LG did a method swap, swapping imp in person to lg_studentInstanceMethod in LGStudent, and then I have to go to lg_studentInstanceMethod in LGPerson, But LGPerson doesn’t have the lg_studentInstanceMethod method, so the imp can’t be found, so it crashes
Optimization: avoid IMP not being found
Try adding the method you want to exchange with class_addMethod
- if
Add a success
If there is no such method in the class, it passesclass_replaceMethod
forreplace
, which is called internallyclass_addMethod
add - If the addition fails, that is, the method is in the class, it passes
method_exchangeImplementations
forexchange
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" the exchange class passed in cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); // Swap methods: swap methods that you don't have -- swap methods that you don't have: Swizzle personInstanceMethod(IMP) -> swiMethod(IMP) swizzle personInstanceMethod(IMP) -> swizzledSEL BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod)); Class_replaceMethod (CLS, swizzledSEL, method_getImplementation(oriMethod), method_getImplementation(oriMethod) method_getTypeEncoding(oriMethod)); }else{// We have method_exchangeImplementations(oriMethod, swiMethod); }}Copy the code
The following isclass_replaceMethod
,class_addMethod
andmethod_exchangeImplementations
Source code implementation of
Among themclass_replaceMethod
andclass_addMethod
Is called inaddMethod
Method, the difference is in the determination of the bool valueaddMethod
Source code implementation of
The subclass is not implemented, and the parent class is not implemented. What’s wrong with the following call?
When the personInstanceMethod method is called, the parent LGPerson is declared but not implemented, and the subclass LGStudent is neither declared nor implemented
//*********LGPerson class ********* @interface LGPerson: NSObject - (void)personInstanceMethod; @end @implementation LGPerson @end //*********LGStudent class ********* @interface LGStudent: LGPerson - (void) helloWord; + (void)sayHello; @end @implementation LGStudent @end //********* call ********* - (void)viewDidLoad {[super viewDidLoad]; LGStudent *s = [[LGStudent alloc] init]; LGStudent *s = [[LGStudent alloc] init]; [s personInstanceMethod]; LGPerson *p = [[LGPerson alloc] init]; [p personInstanceMethod]; }Copy the code
After debugging, found that the running code will crash, error results as shown below
The reason is that the stack overflows and the recursion loops around, so why does recursion happen? —- mainly because personInstanceMethod is not implemented, and then during the method exchange, the oriMethod is never found, and then the exchange is lonely, that is, the exchange fails, when we call personInstanceMethod (oriMethod), So oriMethod is going to go into LG lg_studentInstanceMethod, and that method is going to call lg_studentInstanceMethod, At this point lg_studentInstanceMethod does not refer to oriMethod, and then causes itself to call itself, i.e. recursive infinite loop
Optimization: Avoid recursive infinite loops
- if
oriMethod
Is null, something needs to be done to prevent the method exchange from being discarded because it makes no sense-
Add swiMethod to oriSEL using class_addMethod
-
Using method_setImplementation points the IMP of swiMethod to an empty implementation that does nothing
-
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" the exchange class passed in cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); if (! OriMethod) {// If oriMethod is nil, copy swizzledSEL to an empty implementation that does nothing, as follows: class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ })); } / / general exchange method: exchange yourself in some way, go here Because they have a mean adding methods failure / / way to exchange your no implementation: Swizzle personInstanceMethod(IMP) -> swiMethod(IMP) swizzle personInstanceMethod(IMP) -> swizzledSEL //oriSEL:personInstanceMethod BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); if (didAddMethod) { class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); }else{ method_exchangeImplementations(oriMethod, swiMethod); }}Copy the code
Method-swizzling – Class methods
The principle of method-swizzling for class methods and instance methods is similar, the only difference being that class methods exist in a metaclass, so you can do the following
LGStudent
There are only class methods insayHello
The declaration is not implemented
@interface LGStudent : LGPerson
- (void)helloword;
+ (void)sayHello;
@end
@implementation LGStudent
@end
Copy the code
- Implement the method exchange of class methods in the load method of LGStudent’s class
+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [LGRuntimeTool lg_bestClassMethodSwizzlingWithClass:self oriSEL:@selector(sayHello) swizzledSEL:@selector(lg_studentClassMethod)]; }); } + (void)lg_studentClassMethod{NSLog(@" %s",__func__); [[self class] lg_studentClassMethod]; }Copy the code
- encapsulated
Method exchange of class methods
The following-
You need to get the class method through the class_getClassMethod method
-
When the class_addMethod and class_replaceMethod methods are called to add and replace, the class that needs to be passed in is the metaclass, which can be obtained from the class’s metaclass through the object_getClass method
-
/ / encapsulation method swizzling method + (void) lg_bestClassMethodSwizzlingWithClass (Class) the CLS oriSEL oriSEL: (SEL) swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" the exchange class passed in cannot be empty "); Method oriMethod = class_getClassMethod([cls class], oriSEL); Method swiMethod = class_getClassMethod([cls class], swizzledSEL); if (! OriMethod) {// Avoiding actions makes no sense // When oriMethod is nil, copy swizzledSEL to an empty implementation that does nothing, as follows: class_addMethod(object_getClass(cls), oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); Method_setImplementation (swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){NSLog(@" an empty IMP came "); })); } / / general exchange method: exchange yourself in some way, go here Because they have a mean adding methods failure / / way to exchange your no implementation: Swizzle personInstanceMethod(IMP) -> swiMethod(IMP) swizzle personInstanceMethod(IMP) -> swizzledSEL //oriSEL:personInstanceMethod BOOL didAddMethod = class_addMethod(object_getClass(cls), oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); if (didAddMethod) { class_replaceMethod(object_getClass(cls), swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); }else{ method_exchangeImplementations(oriMethod, swiMethod); }}Copy the code
- Call the following
- (void)viewDidLoad {
[super viewDidLoad];
[LGStudent sayHello];
}
Copy the code
- The run result is as follows, due to the
Method not implemented
, so it will go toEmpty imp
In the
The application of the method – swizzling
The most common use of method-swizzling is to prevent out-of-bounds crashes of arrays, dictionaries, etc
In iOS, NSNumber, NSArray, NSDictionary and so on are all class clusters, and an implementation of an NSArray may consist of multiple classes. Therefore, if you want to Swizzling an NSArray, you must get its “real body” to Swizzling. Directly operating an NSArray is not effective.
The class names of the NSArray and NSDictionary classes are listed below, which can be retrieved by the Runtime function.
The name of the class | The bard |
---|---|
NSArray | __NSArrayI |
NSMutableArray | __NSArrayM |
NSDictionary | __NSDictionaryI |
NSMutableDictionary | __NSDictionaryM |
NSArray, for example
- Create a category of NSArray
CJLArray
@implementation NSArray (CJLArray) // If the following code doesn't work, it's mostly because it calls the super Load method. In the following load method, the load method of the superclass should not be called. + (void)load{Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI")), @selector(objectAtIndex:)); Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(cjl_objectAtIndex:)); method_exchangeImplementations(fromMethod, toMethod); } // If the following code doesn't work, the problem is mostly caused by calling the super load method. In the following load method, the load method of the superclass should not be called. - (id)cjl_objectAtIndex:(NSUInteger)index{if (self.count-1 < index) {self.count-1 < index) { I wouldn't even know it was a mistake. Return [self cjl_objectAtIndex:index]; @try {return [self cjl_objectAtIndex:index]; } @catch (NSException *exception) {// we will print the crash message after the crash. NSLog(@"---------- %s Crash Because Method %s ----------\n", class_getName(self.class), __func__); NSLog(@"%@", [exception callStackSymbols]); return nil; } @finally {} #endif}else{return [self cjl_objectAtIndex:index];} @finally {} #endif}else{return [self cjl_objectAtIndex:index]; } } @endCopy the code
- The test code
NSArray *array = @[@"1", @"2", @"3"];
[array objectAtIndex:3];
Copy the code
- The output is as follows: Crash logs are generated, but the actual crash does not occur