preface
“This is the 10th day of my participation in the August More Text Challenge. For details, see: August More Text Challenge.”
The basic principle of
As we all know, the ultimate superclass of a class is NSObject, and after the program is compiled, it turns out underneath that class isa structure, and each class has an isa pointer that accesses the data in that structure. Method search is in the class method list, through SEL to find the corresponding IMP.
You’ve more or less heard about iOS dark magic, which is method swapping. Apple’s runtime also provides a good environment. Using the Runtime feature of OC, the corresponding relationship between SEL (method number) and IMP (method implementation) is dynamically changed to achieve the purpose of OC method invocation process change. Mainly used in OC method. Here are two examples to illustrate:
- Before the exchange (normal) :
- After the exchange:
Based on these two graphs, we can get a little bit of a sense of what’s going on here. Runtime provides a function to swap SEL and IMP mappings:
OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
Copy the code
The Runtime mechanism provides good support for AOP aspect oriented programming. In OC, we can use Method Swizzling to implement AOP. AOP (Aspect Oriented Programming) is a kind of Programming idea, and OOP is also a kind of Programming idea. But THERE are essential differences between AOP and OOP:
OOP
In terms of programming, he is more inclined to encapsulate business modules, and at the same time can divide more clear business logic units;AOP
The programming idea is to extract and encapsulate sections and extract the common parts in each module, which can improve the reuse rate of modules and reduce the coupling between services.
API
introduce
- through
SEL
Access methodMethod
:
Obtain instance method
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
Copy the code
Get class method
OBJC_EXPORT Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
Copy the code
IMP
thegetter/setter
Methods:
Gets the implementation of a method
OBJC_EXPORT IMP _Nonnull
method_getImplementation(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
Copy the code
Sets the implementation of a method
OBJC_EXPORT IMP _Nonnull method_setimplemementation (Method _Nonnull M, IMP _Nonnull IMP) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);Copy the code
- Gets the encoding type implemented by the method
OBJC_EXPORT const char * _Nullable
method_getTypeEncoding(Method _Nonnull m) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
Copy the code
- Add method implementation
OBJC_EXPORT void
class_addMethods(Class _Nullable, struct objc_method_list * _Nonnull) OBJC2_UNAVAILABLE;
Copy the code
- substitutive
IMP
.
OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
Copy the code
- Swap the two methods
IMP
.
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
Copy the code
- Try to put in simple interest, so that you can only call once, to ensure safety.
Case analysis
Use of interchange methods
Create a class LGPerson, and then create LGTeacher inheriting LGPerson using the following code:
// LGPerson .h @interface LGPerson : NSObject - (void)person_instanceMethod; @end // lgperson. m @implementation LGPerson - (void)person_instanceMethod {NSLog(@"\n print person_instanceMethod: %s\n", __func__); } @end // LGTeacher.h @implementation LGTeacher + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [LGRuntimeUtil lg_methodSwizzlingWithClass:self oriSEL:@selector(person_instanceMethod) swizzledSEL:@selector(teacher_instanceMethod)]; }); } - (void)teacher_instanceMethod {NSLog(@"\n print teacher_instanceMethod: %s\n", __func__); } @ the end / / encapsulation LGRuntimeUtil. M + (void) lg_methodSwizzlingWithClass (Class) the CLS oriSEL oriSEL: (SEL) swizzledSEL:(SEL)swizzledSEL { if (! CLS) NSLog(@" the exchange class passed in cannot be empty "); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swizzleMethod = class_getInstanceMethod(cls, swizzledSEL); method_exchangeImplementations(oriMethod, swizzleMethod); }Copy the code
Initialize both classes in the main file, both calling the person_instanceMethod method:
LGPerson *person = [LGPerson alloc] init];
[person person_instanceMethod];
LGTeacher *teacher = [LGTeacher alloc] init];
[teacher person_instanceMethod];
Copy the code
However, the printed method names are teacher_instanceMethod. That means the substitution is successful.
-
Because the SEL of person_instanceMethod finds the IMP of teacher_instanceMethod, it finds the teacher_instanceMethod method.
-
Person_instanceMethod = person_instanceMethod = person_instanceMethod = person_instanceMethod Continuing with the SEL of the person_instanceMethod method found the IMP after the swap, so the teacher_instanceMethod method was found.
Recursive problem
Teacher_instanceMethod = teacher_instanceMethod;
- (void)teacher_instanceMethod { [self teacher_instanceMethod]; NSLog(@"\n print teacher_instanceMethod: %s\n", __func__); }Copy the code
Error:
Why is that?
-
Person_instanceMethod -> LGPerson:person_instanceMethod
-
Calling person_instanceMethod for LGPerson is calling LGTeacher:teacher_instanceMethod -> LGPerson:teacher_instanceMethod. LGPerson does not implement teacher_instanceMethod, so the error is reported.
So the exchange method must be to exchange their own method.
- Why call yourself?
This class needs to be called again because sometimes the logic needs to be preserved for some processing.
- So how can you avoid this kind of situation?
You can use class_addMethod to try to add methods to be exchanged.
Performance Optimization 1
-
We can use this method to add methods to be exchanged:
-
If the method is added successfully, it does not exist in the class, but it can be replaced by class_replaceMethod, which calls class_addMethod internally to add the method.
-
If that doesn’t work, that means we have this method in our class, so we’ll swap with method_exchangeImplementations
-
-
The code is as follows:
+ (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); BOOL success = class_addMethod(CLS, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod)); Class_replaceMethod (CLS, swizzledSEL, swizzledSEL, swizzledSEL, swizzledSEL, swizzledSEL, swizzledSEL) method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); } else {// method_exchangeImplementations(oriMethod, swiMethod) if I have method_implementations (oriMethod, swiMethod); }}Copy the code
Performance Optimization ii
If person_instanceMethod is not implemented by either of the subclass or the parent class, a call to [self teacher_instanceMethod] in the subclass will result in recursion. If not handled, an error will be returned.
How did it work out? If the method does not exist, you can add an empty implementation of the method after adding the method, which is equivalent to adding an IMP that does nothing:
+ (void)ssl_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) {// When oriMethod is nil, Class_addMethod (CLS, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); Method_setImplementation (swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){NSLog(@" an empty IMP came "); })); } // Try adding the method you want to exchange BOOL Success = class_addMethod(CLS, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod)); If (success) {// if (success) {-- replace -- parent class overpasses a class_replaceMethod(CLS, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); } else {// We have -Swap method_exchangeImplementations(oriMethod, swiMethod); }}Copy the code