This is the 13th day of my participation in the August More Text Challenge. For details, see:August is more challenging

1. Basic introduction

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. In OC, the relationship between SEL and IMP is like the “catalog” of a book.

  • SELIs the method number, just like the title
  • IMPIs the real address of the method implementation, just like the “page number.
  • They areOne to one correspondenceThe relationship between

Runtime provides a function to swap SEL and IMP relationships

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

Using this function to swap SEL and IMP mappings is called Method Swizzle.

Runtime mechanism provides good support for AOP Aspect Oriented Programming. Method Swizzling can be used to implement AOP. AOP (Aspect Oriented Programming) is a Programming idea, which is essentially different from OOP

  • Both OOP and AOP are programming ideas
  • OOP programming ideas are more inclined to encapsulate business modules and divide them into clearer logical units
  • AOP is to extract and encapsulate aspects, extract the common parts in each module, improve the reuse rate of modules, and reduce the coupling between businesses

2. The API is introduced

  • Obtain methods from SEL
    • class_getInstanceMethod: Gets the instance method
    • class_getClassMethod: Gets the class method
  • IMP’s getter/setter methods
    • method_getImplementation: Gets the implementation of a method
    • method_setImplemrntation: 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 IMP of the method. For example, A replaces B, that is, B points to A, A still points to A
  • method_exchangeImplementations: Swap IMP for two methods. For example, A swaps B, that is, B points to A, and A points to B

3. Precautions

3.1 Ensure that method switching is performed only once

To ensure that the exchanged code is executed first, it is sometimes written in the load method, but the load method can also be invoked actively, and if called multiple times, the swapped method may be restored. So we want to make sure that methods can only be swapped once, and we can choose singleton mode so that the swapped methods are not restored

+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self lg_methodSwizzlingWithClass:self oriSEL:@selector(lg_person_say) swizzledSEL:@selector(lg_student_say)]; }); } + (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

3.2 Superclass does not implement methods that subclass will swap

In the parent LGPerson class, we implement the lg_person_say method

#import <Foundation/Foundation.h> 
@interface LGPerson : NSObject 
- (void)lg_person_say; 
@end 
@implementation LGPerson 
- (void)lg_person_say{ 
    NSLog(@"LGPerson:%s",__func__); 
} 
@end
Copy the code

In the subclass LGStudent, we implement the method lg_student_say. In the load method, exchange with the parent class’s lg_person_say method

#import "LGPerson.h" #import <objc/runtime.h> @interface LGStudent : LGPerson @end @implementation LGStudent + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self lg_methodSwizzlingWithClass:self oriSEL:@selector(lg_person_say) swizzledSEL:@selector(lg_student_say)]; }); } + (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); } - (void)lg_student_say{ //lg_studentInstanceMethod -/-> personInstanceMethod [self lg_student_say]; NSLog (@ "LGStudent: % s", __func__); } @endCopy the code

The subclass calls normally, but the parent class cannot find the lg_student_say method

LGPerson: -[LGPerson lg_person_say] LGStudent: -[LGStudent lg_student_say] unrecognized selector sent to instance 0x28218c3f0Copy the code

Method swapping should only affect the current class, but if a subclass is swapping methods from a parent class, the parent class will be affected, and other subclasses that inherit from the parent class will be affected

+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self lg_betterMethodSwizzlingWithClass:self oriSEL:@selector(lg_person_say) swizzledSEL:@selector(lg_student_say)]; }); } + (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)); if (success) { class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); }else{ method_exchangeImplementations(oriMethod, swiMethod); }}Copy the code
  • useclass_addMethodIs added to the current classlg_person_sayMethod, correlationlg_student_saytheimp
  • Returns a value of YES to prove that it is not implemented in the current classlg_person_saymethods
  • If the method was added successfully, useclass_replaceMethodThat will belg_student_sayMethod, replace withlg_person_saytheimp

Above methods:

  • If the subclass implementslg_person_saymethods
    • Add failed, swap directly
    • The parent class is not affected
  • If the subclass is not implementedlg_person_saymethods
    • The new method is associated with imp of lg_student_say
    • Replace lg_student_say with IMP of parent lg_person_say
    • The call order remains: lg_student_say-> lg_person_say
    • Wisdom affects subclasses, not superclasses

3.3 Neither the parent class nor the child class implements the original method

When neither the parent class nor the child class implements the original method, the above method will result in a recursive call to the child class method, resulting in a battle benefit for the following reasons:

  • Subclass added lg_person_say, associated with IMP lg_student_say
  • The parent class does not implement the lg_person_say method; the child class doesclass_replaceMethodLg_student_say will fail, so imp of subclass lg_student_say will not change

Solution, to the original method to increase the realization of the judgment condition

+ (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); if (! OriMethod){IMP IMP = imp_implementationWithBlock(^(id self, SEL _cmd){NSLog(@" make_lg_person_say, nothing "); }); class_addMethod(cls, oriSEL, imp, method_getTypeEncoding(swiMethod)); } BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod)); if (success) { class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); }else{ method_exchangeImplementations(oriMethod, swiMethod); }}Copy the code
  • Determine if the original method is not currently implemented, addlg_person_sayMethod associated with an empty methodimp
  • useclass_addMethodIs added to the current classlg_person_sayMethod, correlationlg_student_saytheimp
  • Due to thelg_person_sayHas been added, the return value must beNO, adding failed
  • usemethod_exchangeImplementations, and swap the two methods directly

4. Exchange of class methods

The difference between Class methods and instance methods: Class methods are stored in the MetaClass method list. Therefore, the MetaClass of the current Class is not used to add or replace aClass method

+ (void)lg_betterClassMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (! CLS) NSLog(@" the exchange class passed in cannot be empty "); Class metaClass = objc_getMetaClass(NSStringFromClass(cls).UTF8String); Method oriMethod = class_getInstanceMethod(metaClass, oriSEL); Method swiMethod = class_getInstanceMethod(metaClass, swizzledSEL); if (! OriMethod){IMP IMP = imp_implementationWithBlock(^(id self, SEL _cmd){NSLog(@" make_lg_person_say, nothing "); }); class_addMethod(metaClass, oriSEL, imp, method_getTypeEncoding(swiMethod)); } BOOL success = class_addMethod(metaClass, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod)); if (success) { class_replaceMethod(metaClass, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); }else{ method_exchangeImplementations(oriMethod, swiMethod); }}Copy the code

5. Array and dictionary method exchange

In ios, classes like NSArray and NSDictionary have class clusters. Because an implementation of An NSArray might consist of multiple classes. So to NSArray, NSDictionary method exchange, must be aligned to the real body operation

The name of the class Class cluster
NSArray __NSArrayI
NSMutableArray __NSArrayM
NSDictionary __NSDictionaryI
NSMutableDictionary __NSDictionaryM

Replace the NSArray objectAtIndex method to avoid array overbounding

@implementation NSArray (LG) + (void)load{ Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:)); Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(lg_objectAtIndex:)); method_exchangeImplementations(fromMethod, toMethod); } - (id)lg_objectAtIndex:(NSUInteger)index{if (self.count-1 < index) {#ifdef DEBUG return [self lg_objectAtIndex:index]; @try {return [self lg_objectAtIndex:index]; } @catch (NSException *exception) {NSLog(@"lg_objectAtIndex crash: %@", [exception callStackSymbols]); return nil; } @finally { } #endif }else{ return [self lg_objectAtIndex:index]; } } @endCopy the code