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
    • OOPThe idea of programming is moreTends to encapsulate business modules, dividing more clear logical units;
    • whileAOPisExtract 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 PpersonInstanceMethodMethod, 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

  • ifAdd a successIf there is no such method in the class, it passesclass_replaceMethodforreplace, which is called internallyclass_addMethodadd
  • If the addition fails, that is, the method is in the class, it passesmethod_exchangeImplementationsforexchange
+ (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_addMethodandmethod_exchangeImplementationsSource code implementation of

Among themclass_replaceMethodandclass_addMethodIs called inaddMethodMethod, the difference is in the determination of the bool valueaddMethodSource 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

  • iforiMethodIs 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

  • LGStudentThere are only class methods insayHelloThe 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
  • encapsulatedMethod exchange of class methodsThe 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 theMethod not implemented, so it will go toEmpty impIn 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 NSArrayCJLArray
@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