Simple to use
Introduction to the
The essence of Method Swizzle is to swap Method implementations (IMP) at run time, typically inserting their own business requirements into existing methods.
The principle of
Objective-c messaging: Calling a method in Objective-C actually sends a message underneath through objc_msgSend(). And the only way to find the message is by the name of the selector method.
[obj doSomething]; /// => objc_msgSend(obj,@selector(doSomething))
Copy the code
Each OC instance object holds an ISA pointer and an instance variable, where the ISA pointer belongs to a class that maintains a MethodLists that can be received at runtime; The mapping between Selector and IMP is kept in the MethodLists. At run time, through selecter to find matching IMP, to find the specific implementation function.
Development can use the dynamic characteristics of Objective-C, at run time to replace the selector corresponding method implementation (IMP), to achieve the purpose of hook. The following figure shows a list of methods using Method Swizzle instead of selector IMP.
example
Print the “description was Swizzle” log before description().
@implementation NSObject (Swizzle) + (void)load{// implement IMP Method originalMethod = class_getInstanceMethod([NSObject) class], @selector(description)); Method newMethod = class_getInstanceMethod([NSObject class], @selector(replace_description)); method_exchangeImplementations(originalMethod, newMethod); } - (void)replace_description{ NSLog(@"Description was Swizzle.");
[self replace_description];
}
@end
Copy the code
What should we be aware of when using Swizzle?
Problem one: inheritance problem
So if originalMethod is implemented by its parent, then method_exchangeImplementations is replacing originalMethod in the parent class, The originalMethod that causes the parent class and other subclasses to call is also replaced
[class_addMethod] [class_addMethod] [class_addMethod]
- Class_addMethod returns YES -> addMethod succeeds. Method does not exist in the class. If you addMethod, the current class has a method that overrides the parent class.
- Class_addMethod returns NO -> addMethod. If method exists, the current method belongs to the current class
- After that, execute exchange
Code:
@implementation Model (Swizzle) + (void)load { Class class = [self class]; SEL originalSelector = @selector(hhh); SEL swizzledSelector = @selector(new_hhh); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // Add originalSelector->swizzle method to class BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));ifClass_replaceMethod (class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else { // 说明originalSelector在当前类中
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
Copy the code
Problem 2: Method parameters can be changed
If the _cmd parameter is used in originalMethod, it may cause a bug
@interface IncorrectSwizzleClass : NSObject
- (void) swizzleExample;
- (void) originalMethod;
@end
@implementation IncorrectSwizzleClass
- (void)swizzleExample {
Method m1 = class_getInstanceMethod([self class], @selector(originalMethod));
Method m2 = class_getInstanceMethod([self class], @selector(replaceImp));
method_exchangeImplementations(m1, m2);
}
- (void)originalMethod {
NSLog(@"The method name is originalMethod, and its _cmd value is :%@",[NSString stringWithFormat:@"* * * - % @", NSStringFromSelector(_cmd)]); } - (void)replaceImp {/* * add your own logic: such as addlog
*/
[self replaceImp];
}
@end
- (void)incorrect {
NSLog(@"#################### incorrect #######################");
IncorrectSwizzleClass* example2 = [[IncorrectSwizzleClass alloc] init];
NSLog(@"## swizzle before calling originalMethod print information:");
[example2 originalMethod];
[example2 swizzleExample];
NSLog(@"## swizzle after calling originalMethod print information:");
[example2 originalMethod];
}
Copy the code
Print result:
The OC method is passed two arguments by default (self & _cmd) [self replaceImp]; /// the compiler changes it to objc_msgSend(self, @selector(replaceImp)). The second argument to the method is @ “replaceImp”.
Solution: C method + method_setImplementation
@interface CorrectSwizzleClass : NSObject - (void) swizzleExample; - (void) originalMethod; @end static IMP __original_Method_Imp; Void replaceImp(id self, SEL _cmd) {/* * add your own logic: for example addlog*/ ((int(*)(id,SEL))__original_Method_Imp)(self, _cmd); } @implementation CorrectSwizzleClass - (void)swizzleExample { Method m = class_getInstanceMethod([self class],@selector(originalMethod)); / / / method_setImplementation:return The previous implementation of the method
__original_Method_Imp = method_setImplementation(m,(IMP)replaceImp);
}
- (void)originalMethod {
NSLog(@"The method name is originalMethod, and its _cmd value is :%@",[NSString stringWithFormat:@"* * * - % @", NSStringFromSelector(_cmd)]);
}
@end
- (void)correct {
NSLog(@"#################### correct #######################");
CorrectSwizzleClass* example = [[CorrectSwizzleClass alloc] init];
NSLog(@"## swizzle before calling originalMethod print information:");
[example originalMethod];
[example swizzleExample];
NSLog(@"## swizzle after calling originalMethod print information:");
[example originalMethod];
}
Copy the code
Print result:
How to achieve object level swizzle?
Swizzle only one object and does not affect other objects
Solution:
- The class itself supports. You can flag if there is a flag to determine whether to execute the method after Swizzle when executing the method. DZNEmptyDataSet (unified blank page)
- Dynamically generates a subclass of the class to which the current object belongs and associates the current object with the subclass. In this way, swizzle’s methods are subclass methods and do not affect the parent class. Consider: Third-party library Aspects
Chat Aspects
Aspects is a library of AOP programming, with no more than 1,000 lines of source code, exposing two methods. Usage: Hook class methods, object instance methods, and three execution locations: before, INSERT, and after
@interface NSObject (Aspects)
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
@end
Copy the code
Example:
/ / [UIViewController aspect_hookSelector:@selector(viewDidLoad) WithOptions: AspectPositionAfter usingBlock: ^ (id < AspectInfo > AspectInfo) {/ * * * add our code to be executed, Since withOptions is AspectPositionAfter. * Each controller's viewDidLoad trigger executes the following method */ [selfdoSomethings];
} error:NULL];
- (void)doSomethings {//TODO: such as log output, statistics code NSLog(@"-- -- -- -- -- -");
}
Copy the code
Simple principle:
- Generate an aliasSelector from an originalSelector
- Prefix the originalSelector to hook with aspects_ -> aliasSelector -> generate an aspectContainer with block & aliasSelector
- Bind aspectContainer to self(object or class) by associated, with key as aliasSelector
- Set originalSelector IMP to _objc_msgForward
- swizzle forwardInvocation
- Get aspectContainer from the custom forwardInvocation with associated & selector -> aliasSelector
- Perform originalSelector & block according to the rules before/insert/after
_objc_msgForward
_objc_msgForward is a function pointer (of the same type as IMP) that is used for message forwarding: when a message is sent to an object and it is not implemented, _objc_msgForward directly forwards the message. Look at an example of a method that doesn’t exist:
❤ ️ hook object
Dynamically generate a subclass of the current object, associate the current object with the subclass, and then replace the forwardInvocation method of the subclass (see source code). You can turn the current object into an instance of a subclass and still use it as the original object for external users, and all swizzle operations happen in the subclass. The advantage of this is that you don’t need to change the class of the object itself
Aspects advantages
- It does not affect other objects
- If you find that the aspect of the current object has been removed when you are in Remove Aspects, then you can repoint the ISA pointer back to the class of the object itself, thus eliminating the swizzle of the object
Aspects shortcomings
- OriginalMethod does not use _cmd in originalMethod
Reference:
Juejin. Cn/post / 684490…
Wereadteam. Making. IO / 2016/06/30 /…
www.cocoachina.com/ios/2017091…