Series of articles:OC Basic principle series.OC Basic knowledge series

From this chapter, we will enter into the research and exploration of the basic knowledge points of OC, and the basic principles of OC will be temporarily concluded in the paragraph (new underlying content will be supplemented later).OC Basic principle series

In this article, we will talk about how we developed and used Method Swizzling.

Method Swizzling foundation

Method Swizzling definition

  • method-swizzlingIs the meaning ofMethods exchange, its main function isReplace the implementation of one method with the implementation of another method at runtimeThat’s what we sayIOS black magic.
  • It can pass in OCMethod-swizzling AOP Programming (Aspect Oriented Programming)AOP to OOP (Object-oriented programming)
  • So each class has its own list of methods, methodList, and methodList has different methods in it called methods, and each Method has sel and IMP of methods in it, and Method interchange isDisconnect sel and IMP original correspondence, and generate sel and new IMP correspondence.

The corresponding relationship is shown in the figure below:

Method Related methods involved in Swizzling

  • class_getInstanceMethod: Gets the instance method
  • class_getClassMethod: Gets the class method
  • method_getImplementation: Gets the method implementation
  • method_setImplementation: Setting method implementation
  • method_getTypeEncoding: gets the encoding of the function, which results in a string of values.
  • class_addMethod: Adds method implementations
  • class_replaceMethod: Replace one method implementation with another, that is, AImp points to BImp, but BImp does not necessarily point to AImp.
  • method_exchangeImplementationsAImp points to BImp, and BImp points to AImp.

Above, we have a basic understanding of Method Swizzling. Now, we will take a look at what problems may occur in the process of using Method Swizzling in the project

Method Swizzling use

The subclass method is not implemented, the superclass is implemented

Let’s write the following code

**********ViewController********** // ViewController.m @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGStudent *s = [[LGStudent alloc] init]; [s personInstanceMethod]; LGPerson *p = [[LGPerson alloc] init]; [p personInstanceMethod]; } @end **********LGPerson********** // LGPerson.h @interface LGPerson : NSObject - (void)personInstanceMethod; + (void)personClassMethod; @implementation LGPerson - (void)personInstanceMethod{NSLog(@"person object method :%s",__func__); } + (void)personClassMethod{NSLog(@"person class method :%s",__func__); } @end **********LGStudent********** // LGStudent.h @interface LGStudent : LGPerson - (void)helloword; @end @implementation LGStudent @end **********LGStudent+LG********** // LGStudent+LG.h @interface LGStudent (LG) @end //  LGStudent+LG.m @implementation LGStudent (LG) + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)]; }); } - (void)lg_studentInstanceMethod{ [self lg_studentInstanceMethod]; NSLog(@"LGStudent class added lg object method :%s",__func__); } @end **********LGRuntimeTool********** @implementation LGRuntimeTool + (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" The exchange class passed cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); method_exchangeImplementations(oriMethod, swiMethod); } @endCopy the code

The personInstanceMethod method declaration is implemented in LGPerson, and the parent of Student is LGPerson. Lg_methodSwizzlingWithClass the method in this case uses method interchange, which means that AImp points to BImp and BImp points to AImp.

Analysis: the reason we write exchange method in + load method, because we know that the load method call will earlier than the main function, the system will automatically help us to introduce, don’t need our own introduction of classification, the system will help us to exchange directly, very save time and effort, and on the load method can timely encapsulation, not seen by the outside world. The reason for using singletons is to prevent the load method from being called repeatedly, causing method exchange to be repeated and losing the meaning of method exchange. However, the load method blocks startup, so methods are sometimes written to initialize.

But if we see lg_studentInstanceMethod calling lg_studentInstanceMethod, is that recursion happening? The answer is no. The reason: We’re swapping lg_studentInstanceMethod and personInstanceMethod in the load method so [self Lg_studentInstanceMethod] is implemented by calling the personInstanceMethod method, so there is no recursion.

Now we run the code and find an error[p lg_studentInstanceMethod]The reason is that we've swapped personInstanceMethod for lg_studentInstanceMethod, so calling personInstanceMethod is actually calling lg_studentInstanceMethod, Since the LGStudent classification implements this method, this is fine. But LGPerson doesn't implement the lg_studentInstanceMethod method, so an error is reported.

Optimize method exchange (deal with IMP not found)

Here’s what we do with the code

[LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)]; Replace [LGRuntimeTool lg_betterMethodSwizzlingWithClass: self oriSEL: @ the selector (personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)]; / / lg_betterMethodSwizzlingWithClass method + (void) lg_betterMethodSwizzlingWithClass: (Class) CLS oriSEL oriSEL: (SEL) swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" The exchange class passed cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); // Switch methods that you don't implement: Swizzle personInstanceMethod(IMP) swizzle personInstanceMethod(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

Run again, we find no error, this is why?

// This is mainly because lg_methodSwizzlingWithClass uses method_exchangeImplementations, Method and lg_betterMethodSwizzlingWithClass is below the BOOL didAddMethod = class_addMethod (CLS, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); if (success) { class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); }else{ method_exchangeImplementations(oriMethod, swiMethod); }Copy the code

Let’s take a look at the source code for class_replaceMethod, class_addMethod, method_exchangeImplementations We found thatBoth class_replaceMethod and class_addMethod call addMethod. The difference is that class_replaceMethod takes the same value as addMethod, and class_addMethod takes the reverse value of addMethodAddMethod ();

  • Line 5695: Checks if the class is already known
  • Line 6602-6607: already exists, line 6604 – get IMP of M if replace is false, line 6606 – set method implementation if replace is true.
  • Line 6609-6625: Not present, line 6609- initialize rWE, line 6612-6619 – add method, line 6623 – flush all caches

The addMethod method method looks to see if CLS doesn’t swap a method, adds it to Methods and returns false, or returns true if it does. But it turns out to be the inverse.

DidAddMethod (swiMethod) is a method that does not exist in swiMethod (swiMethod). If it does not exist in swiMethod (swiMethod), it will be replaced by its parent class. If it does exist, it will be replaced by its parent class. I sometimes have to exchange

Continue optimization (parent class not implemented, subclass not implemented)

With the substitution and swap issues mentioned above, let’s move on to the next issue, which is what happens when the Person class doesn’t implement personInstanceMethod. We run the codeWe found that an error was reported and that the recursion resulted in memory overflow.And the reason for that is personInstanceMethod doesn't implement method_exchangeImplementations. So, there's no implementations of this method exchange failing, so the method will sort of swap itself, so we'll see recursion. Now let’s change the interchange

Will [LGRuntimeTool lg_betterMethodSwizzlingWithClass: self oriSEL: @ the selector (personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)]; Replace [LGRuntimeTool lg_bestMethodSwizzlingWithClass: self oriSEL: @ the selector (personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)]; Lg_bestMethodSwizzlingWithClass * * * * * * * * * * * * * * * * * implementation + (void) lg_bestMethodSwizzlingWithClass (Class) the CLS oriSEL oriSEL: (SEL)  swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" The exchange class passed cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); if (! OriMethod) {// If oriMethod is nil, replace swizzledSEL with 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){NSLog(@" empty IMP "); })); } // If you want to change a method, you can change it. Swizzle personInstanceMethod(IMP) swizzle personInstanceMethod(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

The method is to find that the exchange method is empty, we specify the operation, and we run the codeJudging methods lg_bestMethodSwizzlingWithClass exchange method, is to prevent an infinite loop

  • If the oriMethod is empty, something needs to be done in order to avoid swapping methods that make no sense
    • Add swiMethod to oriSEL using class_addMethod
    • Method_setImplementation points the IMP of swiMethod to our specified implementation, where we print an empty IMP string.

conclusion

From the above explanation we see three different methods of exchange

  • The first one is replacing method_exchangeImplementations which would result in an error returned if the replacement method class is not implemented
  • The second way is if there’s a method that isn’t implemented we’ll swap it class_replaceMethod swaps its own method that isn’t implemented, replaces the implemented method with method_exchangeImplementations. This can lead to recursive loops and errors if the parent class is not implemented either
  • The third way is to replace the unimplemented method with an empty implementation that does nothing if the method is not implemented. This is the best solution

Wrote last

The most common use of method-Swizzling is to prevent arrays from crashing out of bounds, to prevent dictionary assignments from crashing to nil so let’s look at NSMutableDictionary assignments Let’s not swap methods for now, because assigning dic to nil is an errorNow let’s switch the method on, give it nil again, and run itThere’s no error, so you can try to write the array part