The original link

Writing in the front

A classic implementation example:

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        // If class methods are exchanged, use the following code:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}); }#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@".self);
}

@end
Copy the code

I have a few questions about this example:

  1. Why is it interchangeable?
  2. Why do I do it in the load method?
  3. Why not just use itmethod_exchangeImplementationsCan be?

After searching for information, give the following answers.

Why is it interchangeable?

Object invocation methods in Objc, known as messaging, involve the following basic process:

  1. Find the class based on the isa pointer to the object.
  2. In the classobjc_cachemethod_listAccording to method name, find the corresponding method.
  3. If it doesn’t, it looks in its parent up to NSObject.
  4. If it is not found in NSObject, the message forwarding mechanism is triggered. If found, jump to the method implementation that imp points to in method.
  5. If the message forwarding mechanism also fails to process the message, a unreconized selector is returned.

Understand the above process with the Runtime code (simplified).

// Message passing
id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...) ;/ / class
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    struct objc_method_list * _Nullable * _Nullable methodLists;
    struct objc_cache * _Nonnull cache;
}

/ / method
struct method_t {
    SEL name;
    const char *types;
    IMP imp;
}

// IMP declaration
id (*IMP)(id, SEL, ...)
Copy the code

As you can see, if you want to change the implementation of the method, you only need to change the IMP, because it points to the implementation of the method.

Also thanks to Objc’s Runtime System, specific method implementations can be added or replaced to a class at Runtime.

So it’s not that hard to implement method interchange in Objc.

Why do I do it in the load method?

The initialize and load methods are called automatically, so you can choose between them.

Load:

Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.

That is, the load method is called when a class or class is loaded.

As for the Initialize:

Initializes the class before it receives its first message.

When the class sends its first message, the Runtime System sends an Initialize () message to the class.

Obviously, if a load implements method swapping in a class, there are two benefits:

  1. It can be processed as soon as the class is loaded.
  2. Instead of modifying the code of the class, it is handled in the classification.

To ensure that the processing code is executed only once, use dispatch_once.

Why don’t we just use method_exchangeImplementations?

So in the implementation example, we call class_addMethod first, and then we use class_replaceMethod or method_exchangeImplementations based on the result.

Why don’t we just use method_exchangeImplementations?

The reason for this is that the swizzledMethod (used to replace the original method) may not be implemented in this class, but in its parent class, in which case it needs to be added to this class.

Hence the code like this:

// Add the corresponding originalSelector method
OriginalSelector -> swizzledMethod
DidAddMethod is NO if the method already exists
BOOL didAddMethod = class_addMethod(class,
    originalSelector,
    method_getImplementation(swizzledMethod),
    method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
    // originalMethod was added successfully
    SwizzledSelector -> originalMethod
class_replaceMethod(class,
    swizzledSelector,
    method_getImplementation(originalMethod),
    method_getTypeEncoding(originalMethod));
} else {
    // The method already exists, swap directly
    method_exchangeImplementations(originalMethod, swizzledMethod);
}
Copy the code

To consider

Suppose I have two classes, both in the load, that do the same method swap, and then I call the original method, what happens?

Here is a simple code example:

@implementation Cat
- (void)run {
    NSLog(@"ori");
}
@end

@impelmentation Cat (A)
// Load swaps run to a_run

- (void)a_run {
    [self a_run];
    NSLog(@"A");
}
@end

@impelmentation Cat (B)
// Load swaps run to a_run

- (void)a_run {
    [self a_run];
    NSLog(@"B");
}
@end

// What happens when you execute the following code?
[cat run]; // result: ori
Copy the code

The result is also in the code.

If you think about it, it’s easy to understand, you do it twice, you switch back.

The resources

Principle and practice of iOS Development · Runtime: Message Forwarding (Message mechanism, method not implemented +API incompatible crash, simulation of multiple inheritance) – mining gold

Method Swizzling – NSHipster

load() – NSObject | Apple Developer Documentation

initialize() – NSObject | Apple Developer Documentation