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-swizzling
Is the meaning ofMethods exchange
, its main function isReplace the implementation of one method with the implementation of another method at runtime
That’s what we sayIOS black magic
.- It can pass in OC
Method-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 is
Disconnect 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 methodclass_getClassMethod
: Gets the class methodmethod_getImplementation
: Gets the method implementationmethod_setImplementation
: Setting method implementationmethod_getTypeEncoding
: gets the encoding of the function, which results in a string of values.class_addMethod
: Adds method implementationsclass_replaceMethod
: Replace one method implementation with another, that is, AImp points to BImp, but BImp does not necessarily point to AImp.method_exchangeImplementations
AImp 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 addMethod
AddMethod ();
- 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