preface

Before writing method search, when calling a method is, will first go to the class cache for a quick search, found directly execute the corresponding IMP. If not, the bits of the class are slowly searched, and the bits are inserted into the cache. If nothing is found in this step, imp is assigned to forward_IMP. So what happens next, once you’ve done the assignment, let’s explore that.

resolveInstanceMethod

_objc_msgForward_impcache

So let’s see what this forward is.

const IMP forward_imp = (IMP)_objc_msgForward_impcache;
Copy the code

It’s _objc_msgForward_impcache, global search to find out where this is implemented.

Find its implementation in assembly code, which executes __objc_msgForward, which roughly means executes x17, which in turn comes from __objc_forward_handler, and so on. The __objc_forward_handler method is not found by the __objc_forward_handler method. It is possible that this method is not written in assembly, so remove the underline and search globally again.

Can see _objc_forward_handler is objc_defaultForwardStretHandler, and some of them only by the way, whether very familiar, yes this is what we often see the error information can’t find the method of printing. The lookUpImpOrForward() code, however, shows that it doesn’t go there directly, but instead makes a judgment call.

If an IMP is not found and the CLS parent is nil, it will assign imp to forward_IMP and break the loop, and then it will go to this sentence. Let’s draw a picture of what our behavior is.

This step mainly deals with behavior and will be used again later, which is also a key variable. Let’s look at resolveMethod_locked().

Here are mainly made a judgment, whether incoming CLS metaclasses, then according to the situation calls resolveInstanceMethod () or resolveClassMethod (), and then call lookUpImpOrForwardTryCache () and returns the result. These operations are also called message dynamic resolutions, and they are an opportunity that Apple gives when it comes to methods that have not been implemented.

resolveInstanceMethod()

This is its implementation, the bottom fold is some log printing, not important. As you can see, a message is sent to resolveInstanceMethod:, which has the same name as the current method, but is not the same because the method to be sent has a colon. The system (NSObject) implements resolveInstanceMethod, but only returns a NO. A message is sent to resolve_sel, which returns whether it has been processed, and a lookUpImpOrNilTryCache() lookup is performed on sel. Let’s take a look at what lookUpImpOrNilTryCache() does.

It is called _lookUpImpTryCache (), the incoming to basic did not change, only the behaviors to do the processing, behaviors = 1, by now the incoming behaviors | LOOKUP_NIL, calculate the incoming behaviors = 5. In _lookUpImpTryCache(), the CLS is initialized and looks upimporForward () slowly. (The CLS are mostly initialized in the normal order.) The shared cache does not perform the slow search again and returns the slow search result directly. Behaviors = 5 here (at this time to enter the slow when lookup to determine behaviors & LOOKUP_RESOLVER is 5 & 2 = 0, it will no longer resolveMethod_locked (), or it will infinite recursion). Done behavior & LOOKUP_NIL (5 & 4 = 4) and imp == (imp)_objc_msgForward_impcache And then finally return nil.

_lookUpImpTryCache() has already been called once in resolveInstanceMethod(), so why is it called again at the end of resolveMethod_locked()? In fact, the main reason is that there may be multithreading problems, if there are other lines cached first, then we must ensure that the fastest access, is also a speed to improve the operation.

Let’s see how we can implement resolveInstanceMethod without error.

Instance method dynamic resolution example

#import "DDAnimal.h"
#import <objc/message.h>

@interface DDAnimal : NSObject

- (void)run;

@end


@implementation DDAnimal

- (void)sayRun{
    NSLog(@"%@ - %s",self , __func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(run)) {
        NSLog(@"resolveInstanceMethod: %@ - %@",self,NSStringFromSelector(sel));
        IMP sayRunImp = class_getMethodImplementation(self, @selector(sayRun));
        Method method = class_getInstanceMethod(self, @selector(sayRun));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, sayRunImp, type);
    }
    
    return [super resolveInstanceMethod:sel];
}

@end
Copy the code

In this example, you can see that run is only declared, but not implemented. If you call run directly, you will definitely get an error.

SEL (sayRun)resolveInstanceMethod (SEL) SEL (sayRun) SEL (sayRun)

resolveClassMethod()

If the class is metaclass, then resolveClassMethod() is called, which means sel is a class method. ResolveClassMethod () once, but resolveInstanceMethod() again? This is mainly because class methods exist not only as class methods in a class, but also as instance methods in a metaclass. So if you can’t find it in the class method, you look in the metaclass for instance method handling. This can be seen in the isa and succession chart. Let’s look at the code implementation.

You can see that it is basically the same as resolveInstanceMethod(), except that you need to do a little processing on the metaclass to see if it is implemented.

Class method dynamic resolution example

#import "DDAnimal.h"
#import <objc/message.h>

@interface DDAnimal : NSObject

+ (void)jump;

@end


@implementation DDAnimal

+ (void)sayJump {
    NSLog(@"%@ - %s", self, __func__);
}

+ (BOOL)resolveClassMethod:(SEL)sel{

    NSLog(@"resolveClassMethod :%@-%@",self,NSStringFromSelector(sel));

    if (sel == @selector(jump)) {
        id meta = objc_getMetaClass("DDAnimal");
        IMP sayJumpImp     = class_getMethodImplementation(meta, @selector(sayJump));
        Method method    = class_getInstanceMethod(meta, @selector(sayJump));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(meta, sel, sayJumpImp, type);
    }

    return [super resolveClassMethod:sel];
}

@end
Copy the code

One thing to note: because it’s a class method, you go to the metaclass fetch.

conclusion

So when we call a method, the underlying system is actually sending a message, and it’s going to look in the cache very quickly, and if it doesn’t find it it’s going to look in the methodList very slowly, and if it doesn’t find it, it’s going to do a message dynamic resolution, See if it implements resolveClassMethod: or resolveInstanceMethod:. If it still does not, fast message forwarding and slow message forwarding will be performed. Unrecognized selector sent to class, and the whole process ends.