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]

  1. 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.
  2. Class_addMethod returns NO -> addMethod. If method exists, the current method belongs to the current class
  3. 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:

  1. 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)
  2. 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:

  1. Generate an aliasSelector from an originalSelector
    1. Prefix the originalSelector to hook with aspects_ -> aliasSelector -> generate an aspectContainer with block & aliasSelector
    2. Bind aspectContainer to self(object or class) by associated, with key as aliasSelector
  2. Set originalSelector IMP to _objc_msgForward
  3. swizzle forwardInvocation
    1. Get aspectContainer from the custom forwardInvocation with associated & selector -> aliasSelector
    2. 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

  1. It does not affect other objects
  2. 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

  1. OriginalMethod does not use _cmd in originalMethod

Reference:

Juejin. Cn/post / 684490…

Wereadteam. Making. IO / 2016/06/30 /…

www.cocoachina.com/ios/2017091…