In the previous article, we said that if method lookup and dynamic method resolution failed to find a method implementation, then the message forwarding process came into play. This time let’s look at the message forwarding process.

To find the

In fact, to find the message forwarding process is not a simple thing, the most direct way is to see the assembly, but fortunately there are predecessors to explore, here we can also use for reference. We saw a method when exploring the method lookup flow:

log_and_fill_cache

/*********************************************************************** * log_and_fill_cache * Log this method call. If  the logger permits it, fill the method cache. * cls is the method whose cache should be filled. * implementer is the class that owns the implementationin question.
**********************************************************************/
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);
}
Copy the code

SUPPORT_MESSAGE_LOGGING is set to 1 by default, meaning that objcMsgLogEnabled is true to print logs. Where is it set?

instrumentObjcMessageSends

If we follow through and do a global search, we can find this function:

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

When this function is called, objcMsgLogEnabled will be assigned a value, which can be used in our own code:

LGStudent *student = [LGStudent alloc] ;
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
Copy the code

After running the project, go to the/TMP/directory and open the latest msgtimeline file:

Fast forwarding process

File there are a lot of ways, including resolveInstanceMethod we have analyzed the, is a dynamic method resolution, look at the next forwardingTargetForSelector method, the source code is as follows:

+ (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

- (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}
Copy the code

We can’t see anything in the source code, so we have to rely on the official documentation for this:

  • The function of this method is to forward the method that cannot be handled by itself to other objects. That is, the object returned after rewriting this method is the new object to execute sel, but cannot return self, otherwise it will fall into an infinite loop.
  • If you don’t implement it or return nil, you’re going to go to something less efficientforwardInvocation:Method.

Slow forwarding process

When there is no fast forwarding process implementation, will forward to slow process, we find the methodSignatureForSelector: from log printing method, view the source code:

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject instanceMethodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("-[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}
Copy the code

Also look for official documentation:

  • The method is let’s generate oneNSMethodSignatureType and return.
  • The method signature contains information about return value types, parameter types, and so on.

Simply having a method signature will not work, so go to the forwardInvocation: source code:

+ (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
Copy the code

Also view documentation:

  • To use this method, you must first override itmethodSignatureForSelector:Methods.
  • This method can assign multiple objects to receive the message.
  • useanInvocationSends a message to an object. The call saves the result, which the runtime system extracts and passes to the original sender.

If in this method also can’t find method, then jump to doesNotRecognizeSelector: error and collapsed:

// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}
Copy the code

We can see that the error message in this method is actually a common error in our normal development where the method cannot be found.

At this point, the entire message forwarding process is over.