instrumentObjcMessageSends

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

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

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

    objcMsgLogEnabled = enable;
}

static void 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);
}

bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char	buf[ 1024 ];

    // Create/open the log file
    if (objcMsgLogFD == (-1))
    {
        snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
        objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
        if (objcMsgLogFD < 0) {
            // no log file - disable logging
            objcMsgLogEnabled = false;
            objcMsgLogFD = -1;
            return true; }}// Make the log entry
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : The '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}
Copy the code

When objcMsgLogEnabled is true, the logMessageSend function can be called to print out the entire method call flow. Add code

extern voidinstrumentObjcMessageSends(BOOL flag); instrumentObjcMessageSends(YES); . instrumentObjcMessageSends(NO);Copy the code

Go to the TMP folder to view the printed result.

As you can see, there are two functions except dynamic method resolutionresolveClassMethodandresolveInstanceMethodThe program is also executedforwardingTargetForSelector,methodSignatureForSelectoranddoesNotRecognizeSelectorFunction.

forwardingTargetForSelector

In apple document view forwardingTargetForSelector function description

Returns the object to which unrecognized messages should first be directed.

This function can specify another receiver for the method to receive the method. Create classes LKPerson and LKStudent, add method sayHello to LKPerson, and implement method sayHello to LKStudent.

@interface LKStudent : NSObject
@end

@implementation LKStudent
- (void)sayHello{
    NSLog(@"%s",__func__);
}
@end

@interface LKPerson : NSObject
- (void)sayHello;
@end

@implementation LKPerson
- (id)forwardingTargetForSelector:(SEL)aSelector{
    return [LKStudent alloc];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LKPerson *person = [LKPerson alloc];
        [person sayHello];
    }
    return 0; } print result -[LKStudent sayHello]Copy the code

The program does not crash and prints the method [LKStudent sayHello].

methodSignatureForSelectorandforwardInvocation

In apple document view methodSignatureForSelector function specification,

Returns an NSMethodSignature object that contains a description of the method identified by a given selector.

This method must be called simultaneously with the forwardInvocation function. MethodSignatureForSelector function method returns to a signature, forwardInvocation function to reinterpret this method. test

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(sayHello)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [supermethodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation{ LKStudent *p = [LKStudent alloc]; anInvocation.target = p; anInvocation.selector = @selector(studentSayHello); [anInvocation invoke]; } Print result -[LKStudent studentSayHello]Copy the code
  • return [NSMethodSignature signatureWithObjCTypes:"v@:"];Re-sign the method
  • anInvocation.target = p;Respecify the method receiver
  • anInvocation.selector = @selector(studentSayHello);Respecify methodSEL

doesNotRecognizeSelector

+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}
Copy the code

NSObject’s implementation of forwardInvocation: simply invokes the doesNotRecognizeSelector: method; it doesn’t forward any messages. Thus, if you choose not to implement forwardInvocation:, sending unrecognized messages to objects will raise exceptions.

When after slow search, fast search, dynamic method resolution and message forwarding process, still find the method, will throw an exception, call doesNotRecognizeSelector, print error log.