preface

As we learned earlier, Apple provides a message dynamic implementation for fault tolerant management. If the dynamic implementation is not implemented, does it crash directly? The answer is no, Apple has given us another mechanism – message forwarding.

Message forwarding source

In the implementation of lookUpImpOrForward, there is code that deals with message forwarding

    // No implementation found, and method resolver didn't help. // Use forwarding. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst);Copy the code

This section calls objc_msgForward_impcache, and then cache, let’s find the source code

STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
	
	
	ENTRY _objc_msgSend_noarg
	b	_objc_msgSend
	END_ENTRY _objc_msgSend_noarg

	ENTRY _objc_msgSend_debug
	b	_objc_msgSend
	END_ENTRY _objc_msgSend_debug

	ENTRY _objc_msgSendSuper2_debug
	b	_objc_msgSendSuper2
	END_ENTRY _objc_msgSendSuper2_debug

	
	ENTRY _method_invoke
	// x1 is method triplet instead of SEL
	add	p16, p1, #METHOD_IMP
	ldr	p17, [x16]
	ldr	p1, [x1, #METHOD_NAME]
	TailCallMethodListImp x17, x16
	END_ENTRY _method_invoke

#endif

Copy the code

Find this is a piece of assembly source, not easy to understand, let’s see if we can see through the system log, there is a function in the analysis of the source code

log_and_fill_cache(cls, meth->imp, sel, inst, curClass);

Write a journal. Check it out

static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if(! cacheIt)return;
    }
#endif
    cache_fill (cls, 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

ObjcMsgLogEnabled = objcMsgLogEnabled = objcMsgLogEnabled = objcMsgLogEnabled

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;
}

Copy the code

Here we have this function that we can use to log

extern void instrumentObjcMessageSends(BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { LGStudent *student = [LGStudent alloc] ; Dynamic method resolution / / object / / / / class methods - instrumentObjcMessageSends (true);
        [student saySomething];
        instrumentObjcMessageSends(false);

    }
    return 0;
}
Copy the code

After running the program, open the log file/TMP/msgfay-11857

+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent LGStudent methodSignatureForSelector:
- LGStudent LGStudent methodSignatureForSelector:
+ NSString NSObject alloc
+ NSString NSString allocWithZone:
- NSPlaceholderString NSPlaceholderString initWithBytesNoCopy:length:encoding:freeWhenDone:
Copy the code

Found in the system and the two function called forwardingTargetForSelector, methodSignatureForSelector, so these two functions do you use?

Official Documentation

forwardingTargetForSelector

It can be seen that

  • ForwardingTargetForSelector forward for the first time, that is, fast forward
  • The return object of this method is the new object that executes SEL, that is, the object that cannot handle itself will forward the message to others for the processing of the relevant method, but cannot return self, otherwise it will never be found
  • This method is more efficient, if not implemented or nl, the forwardInvocation: method is used for processing
  • The underlying call objc_msgSend(forwardingTarget, sel…) ; To realize the sending of messages
  • The receiver parameters and return values of the forwarded message must be the same as the original method

Code validation: let’s define a new class that implements the interface saySomething

@interface LGTeacher : NSObject

@end

@implementation LGTeacher

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

@end
Copy the code

The message forwarding code is then implemented in the LGStudent class

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) {
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}
Copy the code

The final validation

  LGStudent *student = [LGStudent alloc] ;
  [student saySomething];
Copy the code

The output is:

The 2020-01-07 11:44:50. 947183 + 0800 008 - method to find the message forwarding (19647-237077) - [LGStudent forwardingTargetForSelector:] - saySomething [19647-237077] -[LGTeacher saySomething]Copy the code

methodSignatureForSelector

  • This method generates an NSMethodSignature method signature based on the method selector SEL and returns it. This method signature encapsulates the return value type, parameter type, and other information.
  • ForwardInvocation and methodSignatureForSelector must exist at the same time, the bottom will be through the method signature, generate an NSInvocation, passed as a parameter to invoke them

    As you can see from the documentation

  • Find an object that can respond to the message encoded in the InInvocation. This object need not be the same for all messages
  • The message is sent to this object using the anInvocation. The anInvocation will save the result and the run-time system will pick it up and pass it to the original sender.

Code validation

/ / / / slow forward - bottles unre - don't crash - (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector {NSLog (@"%s -- %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(saySomething)) { // v @ :
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s ",__func__); // SEL aSelector = [Invocation selector];if ([[LGTeacher alloc] respondsToSelector:aSelector])
       [anInvocation invokeWithTarget:[LGTeacher alloc]];
   else[super forwardInvocation:anInvocation]; }}Copy the code

The running results are as follows:

The 2020-01-07 13:38:08. 175563 + 0800 008 - method to find the message forwarding (31283-593368) - [LGStudent methodSignatureForSelector:] - saySomething 2020-01-07 13:38:08.177614+0800 008- Method Lookup - Message Forwarding [31283:593368] -[LGStudent forwardInvocation:] 2020-01-07 13:38:08.178789+0800 008- [LGTeacher saySomething]Copy the code

conclusion

So let’s sum it up with a picture