preface

Before talking about the fast search and slow search of the message sent, if not found will enter the message dynamic resolution, then the message dynamic resolution will directly report an error? Let’s see.

forward

In the lookUpImpOrForward() code, the code below the message dynamic resolution looks like this.

While the message dynamic resolution is returned, the resolveMethod_locked() implementation calls lookUpImpOrForward() and, due to behavior changes, ResolveMethod_locked () is no longer called, so log_and_fill_cache() is called.

In this function, we can see that CLS inserts SEL and IMP into the cache. We have studied this before, but notice that logMessageSend() is used. Look at the source code, global search, find the implementation of the place.

In this function implementation, you can see that the system creates a file and records some function methods. So to implement this function, we have to say objcMsgLogEnabled && Implementer, and implementer is the CLS that’s passed in, which is always there, so objcMsgLogEnabled, search globally and see how it gets assigned.

Can see objcMsgLogEnabled is the default is false, and will be in instrumentObjcMessageSends () to assign a value in this function. All you need to do is call this function to see what methods are logged. Try using it in a non-source environment.

We created a DDAnimal file that declared a class method, but didn’t implement it because we only needed to see the method call, so we set objcMsgLogEnabled to false after the call. Go to the Finder and use the shortcut Command+Shift+G to go directly to/TMP.

See a file for msgbly-71158.

First call resolveClassMethod:, then call resolveInstanceMethod:, this class method lookup order is the same as before. Then call the forwardingTargetForSelector: and methodSignatureForSelector:, finally call doesNotRecognizeSelector:. This a few methods and forwardingTargetForSelector: the only methodSignatureForSelector: no studied, let’s see what these two methods.

Here are a few more:

  1. If the method is implemented, the file will not be generated;
  2. If the implementationresolveInstanceMethod:, then it will not be recordedforwardingTargetForSelector:andmethodSignatureForSelector:And the methods and functions that follow.

forwardingTargetForSelector:

So how does this work? If you look in the source code, it’s just return nil. For methods that don’t make sense, you can look them up in Dash.

returns the object to which unrecognized messages should first be directed. Return the object to which the unrecognized method should point.

// Add a method to the ddAnimal. m file.
// If it is a class method, it returns the class to which it refers; if it is an object method, it returns the instance object to which it refers.
+ (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [DDCat class];
}

#import "DDCat.h"

@implementation DDCat

// The object being pointed to must implement a class method or object method of the same name.
+ (void)run {
    NSLog(@"%s",__func__);
}

@end
Copy the code

Print the result

2021-07-07 18:12:12.522777+0800 res[98732:1778794] +[DDAnimal forwardingTargetForSelector:] - run
2021-07-07 18:12:12.523866+0800 res[98732:1778794] +[DDCat run]
Copy the code

You can see that it printed successfully without any errors. This method is also called fast forward, and its main function is to treat the object returned by the method as the new object received by the method.

methodSignatureForSelector:

Do the same to Dash for usage.

Returns an NSMethodSignature object that contains a description of the method identified by a given selector. Return an NSMethodSignature object that contains the method identifier, which is the method type. And the method has a forwardInvocation method:, which you can see at the bottom of the Dash lookup.

Look at the code implementation.

// + (void)run; Is of type v@:, because the underlying method will be converted with two arguments by default, a receive object and sel(_cmd).
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(run)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// There is an associative method in Dash. This analogy must be implemented. Methods can be handled here or not. But it has to, or it crashes.
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%@ -- %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
Copy the code

Print the result

You can see that there are no more crashes and the log is successfully printed. See how the forwardInvocation works: how it works.

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%@ -- %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
    if ([self respondsToSelector:anInvocation.selector]) {
        [anInvocation invoke];
    }else if ([DDCat respondsToSelector:anInvocation.selector]){
        [anInvocation invokeWithTarget:DDCat.class];
    }else{
        NSLog(@"%s -- %@", __func__, NSStringFromSelector(anInvocation.selector)); }}Copy the code

Print the result and call successfully.

As you can see, this method invocation is complex and has the forwardInvocation:, which is relatively inefficient. This step is also called slow forward, forward if slow or no treatment, so the probability is called doesNotRecognizeSelector:, directly collapsed.

Exploring message forwarding logic

We have just seen the logging of method calls, so we can guess such a process, but we don’t know how the underlying logic does, so can we explore it? Now it’s doing nothing, it’s still just declaring a class method run, it’s not implemented, let’s run it and see if there’s anything in the stack that’s available. The LLDB executes the BT instruction to get the stack information.

Previous execution can see the call doesNotRecognizeSelector ___forwarding___ and _CF_forwarding_prep_0, maybe that is the key to global search and see how to implement. Search ___forwarding___ and find no information, remove the underline and try.

Although there are results this time, but the basic are some notes, useless, how to do? ___forwarding___ is in CoreFoundation. Do you want to download the CoreFoundation source code? After a wave of operations, the source code is found, but the search turns up nothing.

Several methods have been tried, this time it is only to try disassembly view.

Create a breakpoint at any point in the iOS project, then LLDB executes the command image list, you can see all the local image library files. Find CoreFoundation. Copy the path URL and go to finder to find the library. Then open it with Hopper. A search for ___forwarding___ in Hopper yields the following result.

There is a lot of code in this method, but don’t worry, we just need to remember our goal, which is to find the code logic for the forwarding process.

The figure above is the code logic of message forwarding, which basically confirms our previous conjecture.

Message Forwarding Process

The figure above shows the process of message forwarding.

conclusion

If no corresponding method is found after the message is searched, the message forwarding process is entered. The message forwarding process is divided into fast forwarding and slow forwarding. Fast forwarding simply requires specifying an object that responds to the method. Slow forwarding requires a method signature, which is then processed in the forwardInvocation and forwarded to the object that can respond, or not, and that’s it.