1. InstrumentObjcMessageSends analysis

We did not find imp in the slow lookup before, but what if the method resolution is still not handled? What we do in a slow lookup is we cache the methods that we find, log_and_fill_cache

log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer){#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if(! cacheIt)return;
    }
#endif
    cls->cache.insert(sel, imp, receiver);
}
Copy the code

There’s an objcMsgLogEnabled judgment, but bool objcMsgLogEnabled = false. Assignment of global search objcMsgLogEnabled found instrumentObjcMessageSends face it for the change

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)/ / objcMsgLogEnabled false by default
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);// By opening the words, exposing all the method caches we can get some traces of the use

    // Sync our log file
    if(objcMsgLogFD ! = -1)
        fsync (objcMsgLogFD);// Write log

    objcMsgLogEnabled = enable;/ / assignment
}
Copy the code

If we are to expose the method cache log the instrumentObjcMessageSends set to yes can get the log cache method. We can’t find this method directly, so we’re going to expose this method at this point

extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        KBStudent *student = [KBStudent alloc];
        instrumentObjcMessageSends(YES);
        [student sayHello];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
Copy the code

We just focus on this method, and then we close it. The logMessageSend method says the log path

  snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
Copy the code

get

We just have to focus onKBStudentIt is good,resolveInstanceMethod->forwardingTargetForSelector->methodSignatureForSelector->resolveInstanceMethod->doesNotRecognizeSelectorHere can also be explained as before the societyresolveInstanceMethodLeft time

2. ForwardingTargetForSelector analysis

Source search found no introduction, only implementation in NSObject. We Command + shift + 0 (the number 0) to query the official documentation about this method

Return an object that does not implement the message for the original object, let the object try, that is, method redirection. Instance object A doesn’t implement this method, so I’m going to try instance object B and return an instance object B.

#import "KBPerson.h"
@implementation KBStudent

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    return [KBPerson alloc];
}
@end
Copy the code
2021-07-03 11:23:03232618.+0800Message forwarding [19642:2342779] -[KBPerson sayHello]
Copy the code

The sayHello method was implemented in KBPerson without crashing. This is the way to fast forward. Class method

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    return KBPerson.class;
}
Copy the code

But in reality, there is no way to tell which class is implemented and which is not. We can create a class, add unimplemented methods to the class, and handle them uniformly. You can create a class and add class methods and instance methods for uniform processing. In the Category of NSObject forwardingTargetForSelector class methods and instance methods.

#import "NSObject+AvoidCrash.h"
#import "KBPerson.h"
#import "KBErrorObject.h"
#import<objc/message.h> @implementation NSObject (AvoidCrash) -(id)forwardingTargetForSelector:(SEL)aSelector { KBErrorObject *  error = [KBErrorObject alloc]; IMP imp = class_getMethodImplementation(KBErrorObject.class, @selector(sayNB)); Method method = class_getInstanceMethod(KBErrorObject.class, @selector(sayNB));const char *type = method_getTypeEncoding(method);

    class_addMethod(KBErrorObject.class, aSelector, imp, type);


    return error;
}

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    Class metaClas = object_getClass(KBErrorObject.class);
    IMP imp = class_getMethodImplementation(metaClas, @selector(say666));
    Method method = class_getClassMethod(metaClas, @selector(say666));
    const char *type = method_getTypeEncoding(method);
    class_addMethod(metaClas, aSelector, imp, type);
    return KBErrorObject.class;
}
@end
Copy the code

This allows for uniform processing at the time of invocation

The method of fast forwarding is that Apple asks us to change the person to handle the message when the dynamic resolution is not implemented.Replacement to achieve.

2. MethodSignatureForSelector analysis

Then before the crash logs, if fast forwarding not implemented, will be called methodSignatureForSelector.

This method is also used for must createNSInvocationObject, we override this method to return the appropriate method signature when the corresponding method is not implemented. This method may not be handled, but it is important to note that you need to rely on it to solve the problemforwardInvocationThe implementation of the.

We are inNSObjecttheCategoryIn the implementationThe signatureandforwardInvocation

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature * signature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
    
    returnsignature; } - (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"%s",__func__);
}
Copy the code

Message forwarding is not implemented and there is no send crash. It’s basically telling the system that I know this unimplemented method and I don’t have to deal with it. But you can deal with it if you want to.

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    anInvocation.target = [KBPerson alloc];
    
    [anInvocation invoke];
    
   
}
Copy the code
2021-07-03 15:03:40.337782+0800Message forwarding [20554:2455376] -[KBPerson sayHello]

Copy the code

So this is basically telling the KBPerson instance object to implement it, just like the quick forward, to redirect it to somebody else. The same goes for class methods.

3. Analysis of Hopper

This is the source code analysis of the method crash process, let’s use Hopper analysis to analyze ourCoreFoundationintoHoppersearchforward. choose__forwarding__Then look at the pseudo code implementationEnter after dynamic resolution failsFast forward. It will be done systematicallyloc_64a67The slow forwarding process is displayedjudgemethodSignatureForSelectorWhether method signatures are implemented. Here we goforwardStackInvocation:But when I rewrite itThere is no responseIt should be a systematic approach. Continue to seeforwardInvocation:Implement or not (forward help, method redirection). The final call is not implementeddoesNotRecognizeSelector:An error.

3.1 to walk again after methodSignatureForSelector resolution concerning the dynamic analysis

methodSignatureForSelectorAfter another dynamic resolution, we useHopperLet’s analyze the clickmethodSignatureForSelectorSelect instance MethodgetTo view___methodDescriptionForSelectorThe specific implementation

If the jump is not implementedloc_7c68bget

Searching in source code

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if(! cls || ! sel)return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}
Copy the code

A slow lookup process lookUpImpOrForward is performed, which is why the dynamic resolution is performed again later.

4. To summarize

  • The slow search did not find the method that will proceedDynamic resolution.resolveInstanceMethodorresolveClassMethodTo re-assign an implementation imp to the current method.
  • A message will be sent after the dynamic resolution failsFast forward forwardingTargetForSelectorLet another object or class implement the method, i.eMethod redirection.
  • Fast forwarding failed to enterSlowly forward. First of all,methodSignatureForSelectorTo re-sign a method, you must override itforwardInvocation. If you implement this method right hereforwardInvocationTo perform redirection.
  • In the end, the system was not implementedCoreFoundationWill be calledclass_getInstanceMethodPerform a method dynamic resolution.
  • Message forwardingThe flow chartThe following