What is method-swizzling?

  • Method-swizzling means method swapping, and its main function is to replace the implementation of one method with the implementation of another method at runtime. This is often referred to as iOS dark magic.

  • In OC is the use of method-Swizzling implementation of AOP, AOP(Aspect Oriented Programming, section Oriented Programming) is a kind of Programming ideas, different from OOP (object-oriented Programming)

    • OOP and AOP are both programming ideas
    • OOPProgramming thinking is moreFavors encapsulation of business modules, divided into clearer logical units;
    • whileAOPisSection-oriented extraction and encapsulation is carried out 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, namely methodList, methodList has different methods, namely Method, each Method contains sel and IMP Method, Method exchange is to disconnect SEL and IMP original corresponding, and sel and new IMP generate corresponding relationship

The corresponding relationship between SEL and IMP before and after exchange is shown in the figure below

Method-swizzling related API

  • Obtain Method from SEL

    • Class_getInstanceMethod: method of obtaining an instance

    • Class_getClassMethod: Gets the class method

  • Method_getImplementation: Gets an implementation of a method

  • Method_setImplementation: Sets the implementation of a method

  • Method_getTypeEncoding: Gets the encoding type of the method implementation

  • Class_addMethod: Adds the method implementation

  • Class_replaceMethod: Replace the implementation of one method with the implementation of another method, that is, aIMP points to bIMP, but bIMP does not necessarily point to aIMP

  • Method_exchangeImplementations: Swap implementations of two methods, that is, aIMP -> bIMP, bIMP -> aIMP

Pothole 1: One-time problem during the use of method-Swizzling

The mehod-swizzling is written in the load method, and the load method will be called many times, which will cause the method to swap repeatedly, so that the method SEL pointing back to the original IMP problem

The solution

The singleton design principle allows method exchange to be performed only once, as can be done with dispatch_once in OC

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [LGRuntimeTool lg_bestMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
    });
}

Copy the code

Pitfall 2: the subclass is not implemented, the parent class is implemented

In the following code, LGPerson implements personInstanceMethod, while LGStudent inherits from LGPerson and does not implement personInstanceMethod. What is the problem with running 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]; [s personInstanceMethod]; LGPerson *p = [[LGPerson alloc] init]; [p personInstanceMethod]; }Copy the code

Where, the method interchange code is as follows, through 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 a personInstanceMethod method for you // imp - (void)lg_studentInstanceMethod{// add a personInstanceMethod method for you Does //// generate recursion? No recursion is generated because lg_studentInstanceMethod goes to oriIMP, the implementation of personInstanceMethod [self lg_studentInstanceMethod]; NSLog(@"LGStudent class added lg object method :%s",__func__); } @endCopy the code

Here is the wrapped method-Swizzling method

@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); }Copy the code

After debugging the actual code, it is found that it crashes when p calls the personInstanceMethod method, as described below

  • [s personInstanceMethod]; Student imp is swapped to lg_studentInstanceMethod, and LGStudent has this method (in LG), so it does not return an error

  • The point of collapse is [p personInstanceMethod]; , the essential reasons are: So I’ve swapped imp from person to lg_studentInstanceMethod from LGStudent, and then I need to go to LGPerson to lg_studentInstanceMethod, But LGPerson doesn’t have the lg_studentInstanceMethod method, which means the imp associated with it can’t be found, so it crashes

Optimization: avoid imp can not find

Try adding the method you want to swap with class_addMethod

  • ifAdd a success, that is, the method does not exist in the classclass_replaceMethodforreplaceIs called internallyclass_addMethodadd
  • If the addition is unsuccessful, that is, the method exists in the class, passesmethod_exchangeImplementationsforexchange
+ (void)lg_betterMethodSwizzlingWithClass:(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); // Switch methods that you don't implement: Swizzle personInstanceMethod(IMP) swizzle personInstanceMethod(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_getTypeEncoding(oriMethod)); }else{// Implementations(oriMethod, swiMethod); }}Copy the code

Here are the source implementations of class_replaceMethod, class_addMethod, and method_exchangeImplementations

Where class_replaceMethod and class_addMethod are called addMethod method, the difference lies in the bool value judgment, the following is the source code of addMethod implementation

Pothole 3: 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, there is only a declaration in the parent LGPerson class and no implementation, and there is neither declaration nor implementation in the subclass LGStudent

//*********LGPerson class ********* @interface LGPerson: NSObject - (void)personInstanceMethod; @implementation LGStudent @end //*********LGStudent ********* @interface LGStudent: LGPerson - (void) helloWord; + (void)sayHello; @end@implementation LGStudent @end //********* call ********* - (void)viewDidLoad {[super viewDidLoad]; LGStudent *s = [[LGStudent alloc] init]; [s personInstanceMethod]; LGPerson *p = [[LGPerson alloc] init]; [p personInstanceMethod]; }Copy the code

After debugging, it is found that the running code will crash, and the error result is as follows

The reason is stack overflow, recursion is endless, so why does recursion happen? —- is mainly because the personInstanceMethod is not implemented, and the oriMethod is never found when the method is swapped, and the exchange fails. When we call personInstanceMethod (oriMethod), So oriMethod goes into LG lg_studentInstanceMethod, and then lg_studentInstanceMethod is called in that method, Lg_studentInstanceMethod does not point to the oriMethod, causing it to tune itself in a recursive loop

Optimization: Avoid recursive loops

  • iforiMethodEmpty, something needs to be done in order to avoid method interchange being abandoned without meaning anything
    • Add swiMethod to oriSEL by class_addMethod

    • 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 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){ })); } // 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

Method-swizzling – Class method

The method-Swizzling principle of class methods and instance methods is similar, the only difference is that class methods exist in metaclespaces, so you can do the following

  • LGStudentThere are only class methods insayHelloDeclaration, not implemented
@interface LGStudent : LGPerson
- (void)helloword;
+ (void)sayHello;
@end

@implementation LGStudent

@end

Copy the code
  • Implement method interchange of class methods in LGStudent’s classified load method
+ (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(@"LGStudent :%s",__func__); [[self class] lg_studentClassMethod]; }Copy the code
  • encapsulatedMethod interchange 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 a metaclass, which can get the metaclass of the class 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 cannot be empty "); Method oriMethod = class_getClassMethod([cls class], oriSEL); Method swiMethod = class_getClassMethod([cls class], swizzledSEL); if (! OriMethod) {// If oriMethod is nil, replace swizzledSEL with 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(@" there's an 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(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 result of the run is as follows, since the match method is not implemented, it will walk into an empty IMP

The application of the method – swizzling

The most common use of method-Swizzling is to prevent arrays, dictionaries, and so on from crashing out of bounds

In iOS, NSNumber, NSArray, NSDictionary and other classes are class clusters. An implementation of NSArray may consist of multiple classes. Therefore, if you want to Swizzling NSArray, you must obtain its “real body” to Swizzling, direct operation on NSArray is invalid.

The NSArray and NSDictionary classes are listed below, and 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 class of NSArrayCJLArray
@implementation NSArray (CJLArray) // If the following code doesn't work, most of the problem is because it calls the super load method. In the load method below, the parent class's load method 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 does not work, the problem is mostly caused by calling the super load method. In the load method below, the parent class's load method should not be called. - (id)cjl_objectAtIndex:(NSUInteger)index{if (self.count-1 < index) {if (self.count-1 < index) { You wouldn't even know it was a mistake. Return [self cjl_objectAtIndex:index]; #else @try {return [self cjl_objectAtIndex:index]; } @catch (NSException *exception) { NSLog(@"---------- %s Crash Because Method %s ----------\n", class_getName(self.class), __func__); NSLog(@"%@", [exception callStackSymbols]); return nil; } @finally {} #endif}else{// If there is no problem, return [self cjl_objectAtIndex:index]; } } @endCopy the code
  • The test code
 NSArray *array = @[@"1", @"2", @"3"];
[array objectAtIndex:3];

Copy the code
  • A crash log is printed as follows, but it does not crash