For Method Swizzling, the core code is a Runtime C API:

Code sample

Take the page statistics requirement mentioned above. This requirement is common in many companies, and the following Demo uses Method Swizzling to implement this requirement.

So we’re going to add a Category to UIViewController, and then we’re going to add Method Swizzling to the +(void)load Method in that Category, and the Method that we’re going to replace is also in that Category. Since the Load class method is a method that is called when the program is running when the class is loaded into memory, it executes early and does not require us to call it manually. And this method is unique, that is, it is called only once, without worrying about resource grabbing. Method Swizzling’s replacement Method name must be unique, at least in the class to be replaced.

#import "UIViewController+swizzling.h"
#import <objc/runtime.h>Implementation UIViewController (swizzling) + (void)load {// implement UIViewController (swizzling) + (void)load {// implement UIViewController (swizzling) + (void)load {// implement UIViewController (swizzling) + (void)load List gets the method structure, or if it's a class method, use the class_getClassMethod() function. Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad)); Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad)); /** We use the class_addMethod() function here to perform a layer of validation on Method Swizzling, which will fail if self does not implement the swapped Method. And self doesn't have a method implementation that swaps, but the superclass does, so it calls the superclass's method, and the result is not what we want. So we're checking class_addMethod() here, and if self implements this method, class_addMethod() will return NO, and we can swap it. * /if(! class_addMethod([self class], @selector(swizzlingViewDidLoad), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) { method_exchangeImplementations(fromMethod, toMethod); }} // The method we implement ourselves, which swaps with self's viewDidLoad method. - (void)swizzlingViewDidLoad { NSString *str = [NSString stringWithFormat:@"% @", self.class]; // We add a judgment here to remove objects from the system's UIViewControllerif(! [str containsString:@"UI"]){
        NSLog(@"Statistics: %@", self.class);
    }
    [self swizzlingViewDidLoad];
}
@end

Copy the code

You’re calling self swizzlingViewDidLoad again in swizzlingViewDidLoad;

Doesn’t that make recursive calls?

A: However…. Will not.

The implementation principle of Method Swizzling can be understood as “Method interchange”. Suppose we swap methods A and B, and send A message to method A but execute B, and send A message to method B. For example, in our code above, when the system calls the viewDidLoad method of UIViewController, it’s actually executing the swizzlingViewDidLoad method that we implemented. And inside swizzlingViewDidLoad we call [self swizzlingViewDidLoad]; UIViewController’s viewDidLoad method.

Method Swizzling class cluster

As I said before, during the development of our project, we often crashed because the NSArray array was out of bounds or the KEY or value of THE NSDictionary was nil. For these problems, Apple did not give a warning, but directly crashed. It felt that Apple was really “too harsh”.

We can Swizzling NSArray, NSMutableArray, NSDictionary, NSMutableDictionary and other classes according to the above example. But… Method Swizzling doesn’t work and the code is correct. What the hell is that? This is because Method Swizzling doesn’t work with NSArray and other classes. Because these classes, clusters of classes, are actually a kind of abstract factory design pattern. Inside the abstract factory there are many other subclasses that inherit from the current class, and the abstract factory class creates different abstract objects to use depending on the situation.

For example, if we call NSArray’s objectAtIndex: method, this class will check inside the method and create different abstract classes to operate on. So what we’re doing to the NSArray class is really just doing to the parent class, and inside NSArray we’re going to create other subclasses to do that, and it’s not really doing that to the NSArray itself, so we’re going to do that to the NSArray itself.

Code sample Below we implemented to prevent NSArray because call objectAtIndex: method, take when an array subscript lead to the collapse of the:

#import "NSArray+LXZArray.h"
#import "objc/runtime.h"

@implementation NSArray (LXZArray)

+ (void)load {
    Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(lxz_objectAtIndex:));
    method_exchangeImplementations(fromMethod, toMethod);
}

- (id)lxz_objectAtIndex:(NSUInteger)index {
    if(self.count-1 < index) {// do some exception handling here, otherwise you don't know the error. @try {return[self lxz_objectAtIndex:index]; } @catch (NSException *exception) { NSLog(@"---------- %s Crash Because Method %s ----------\n", class_getName(self.class), __func__);
            NSLog(@"% @", [exception callStackSymbols]);
            return nil;
    }
        @finally {}
    } else {
        return [self lxz_objectAtIndex:index];
    }
}
@end

Copy the code

__NSArrayI is the true class of NSArray, and NSMutableArray is different 😂. We can get the real class from the Runtime function:

objc_getClass("__NSArrayI");
Copy the code

For example,

Here are some examples of common class clusters:

  • NSArray -> __NSArrayI
  • NSMutableArray -> __NSArrayM
  • NSDictionary -> __NSDictionaryI
  • NSMutableDictionary -> __NSDictionaryM

Thanks to author: Liu Xiaozhuang