Method Swizzling principle

Method Swizzling can help us swap the two methods at run time to ensure AOP (aspect-oriented Programming) without changing the way business Aspect object Programming is done. The essence of Method Swizzling is to swap IMP and SEL.

AOP is a Programming paradigm, can also be said to be a Programming idea, using AOP can solve OOP (Object Oriented Programming, object-oriented Programming) because of the aspect requirements caused by a single responsibility broken problem. AOP makes it easy to plug in aspect requirements functionality without intruding into OOP development.

Method Swizzling use

Create a new classification and swap the Method Swizzling Method in the classification.

#import "UIViewController+Swizzling.h"
#import <objc/runtime.h>@implementation UIViewController (Swizzling) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; // When swizzling a class method, use the following: // Class class = object_getClass((id)self); SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(eb_viewWillAppear:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // the class_addMethod() function returns success to indicate that the swapped method is not implemented, and is implemented first by class_addMethod(); Return failure means that the method has been exchanged, can be directly IMP pointer exchangeif(class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), Method_getTypeEncoding (swizzledMethod)) {class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{// Swap IMP pointer method_exchangeImplementations(originalMethod, swizzledMethod); }}); }#pragma mark - Method Swizzling- (void)eb_viewWillAppear:(BOOL)animated {// Self eb_viewWillAppear:animated]; }Copy the code

Method Swizzling pay attention

  • Execute in the +load method

The +load method is called when the class is initially loaded, before the main function, and loaded in compile order. If methods are swapped at other times, it is difficult to guarantee that the swapped method will not be called in another thread at the same time, causing the program to fail to execute as expected.

  • The method to be swapped must be a method of the current class, not a method of the parent class.

For example, if the B method of superclass A swaps with the D method of subclass C, the expectation is that the B method of executing A calls D method, and the D method of executing C calls B method. In fact, the B method that executes the parent class A still calls the B method. Copying the implementation of the parent class directly to the child class doesn’t work either.

  • If the exchange method relies on CMD, then all sorts of strange problems can occur if the CMD changes after the exchange, and these problems can be difficult to troubleshoot. In particular, if you swap system methods, you can’t guarantee that the system methods internally depend on CMD.

__cmd is a hidden argument that exists in every function, and _cmd represents the selector of the current method in objective-C methods, just as self represents the object instance of the current method call. It is obvious that the CMD of the original method is different after the method swap, and if some of the logic of the original function is applied to _cmd, then a strange error will occur.

For example, a method of the Person class, which internally relies on __cmd to make some logical judgments, normally executes the run method and prints three steps.

#import "Person.h"
#import <objc/runtime.h>
@implementation Person

- (void)run{
    NSString *str = NSStringFromSelector(_cmd);
    if (str.length == 3) {
        NSLog(@"Ran three steps.");
    }else{
        NSLog(@"Run the %ld step", str.length);
    }
}
@end
Copy the code

Exchange the Run method of the Person class.

#import "Person+Swizzling.h"
#import <objc/runtime.h>@implementation Person (Swizzling) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; // When swizzling a class method, use the following: // Class class = object_getClass((id)self); SEL originalSelector = @selector(run); SEL swizzledSelector = @selector(my_run); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // the class_addMethod() function returns success to indicate that the swapped method is not implemented, and is implemented first by class_addMethod(); Return failure means that the method has been exchanged, can be directly IMP pointer exchangeif(class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), Method_getTypeEncoding (swizzledMethod)) {class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{// Swap IMP pointer method_exchangeImplementations(originalMethod, swizzledMethod); }}); }#pragma mark - Method Swizzling
- (void)my_run {
    
    [self my_run];
   
}
Copy the code

Swap and then run, print out, run 6 steps.

  • Method to exchange naming conflicts

If a method exchange is followed by a method with the same name elsewhere, the method exchange may fail.

#import "UIView+MyViewAdditions.h"
#import <objc/runtime.h>
typedef IMP *IMPPointer;

@implementation UIView (MyViewAdditions)

static void MySetFrame(id self, SEL _cmd, CGRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, CGRect frame);

static void MySetFrame(id self, SEL _cmd, CGRect frame) {
    // do custom work
    
    SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return(imp ! = NULL); } + (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {return class_swizzleMethodAndStore(self, original, replacement, store);
}
Copy the code

The above method of using function Pointers effectively prevents naming conflicts.

  • Swizzling should always be performed in dispatch_once

This prevents multiple executions of the swap method, which may result in no swap at all.

For more on the risks of Method switching at runtime, check out the Stackoverflow question discussion “What are the Dangers of Method Swizzling in Objective C?” .