Summary of basic principles of iOS
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
OOP
Programming thinking is moreFavors encapsulation of business modules
, divided into clearer logical units;- while
AOP
isSection-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
: Gets the instance methodclass_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];
The error is not reported becausestudent
In theimp
Exchange becamelg_studentInstanceMethod
And theLGStudent
There is this method (in LG classification), so no error will be reported- The breaking point is
[p personInstanceMethod];
, the essential reasons are:LGStudent
The classification ofLG
In theMethods exchange
That will beperson
In theimp
Exchange becameLGStudent
In thelg_studentInstanceMethod
And then you need to goLGPerson
In looking forlg_studentInstanceMethod
, butLGPerson
There is nolg_studentInstanceMethod
Methods, i.e.,The associated IMP could not be found
“So it crashed
Optimization: avoid imp can not find
Try adding the method you want to swap with class_addMethod
- if
Add a success
, that is, the method does not exist in the classclass_replaceMethod
forreplace
Is called internallyclass_addMethod
add - If the addition is unsuccessful, that is, the method exists in the class, passes
method_exchangeImplementations
forexchange
+ (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
- The following is
class_replaceMethod
,class_addMethod
andmethod_exchangeImplementations
Source code implementation
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
-
If the oriMethod is empty, a few things need to be done to prevent the method exchange from being discarded without meaning anything
- through
class_addMethod
tooriSEL
addswiMethod
methods - through
method_setImplementation
willswiMethod
theIMP
Point to theAn empty implementation that does nothing
- through
+ (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
LGStudent
There are only class methods insayHello
Declaration, 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
The method interchange for encapsulated class methods is as follows
- Need to pass through
class_getClassMethod
methodsGet class methods
- In the call
class_addMethod
andclass_replaceMethod
The classes that need to be passed in when methods are added and replaced areThe metaclass
, the metaclass can passobject_getClass
Method gets the metaclass of the class
/ / 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 run result is as follows, due to fit
Method not implemented
So it goes toEmpty imp
In the
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 NSArray
CJLArray
@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