preface

We got a glimpse of dynamic method resolution in the previous article OC – Underlying Dynamic Method Resolution. Today we will explore the forwarding of messages, the last process of message delivery. There are friends who have not downloaded objC source can be downloaded here. Today we need to prepare the CF source code and the disassembly tools Hopper and IDA.

Message forwarding

In a lookUpImpOrForward slow query, an IMP from a metaclass enters the DONE process. Determine the shared cache in done and go to the log_and_fill_cache function.

Add a cache to the log_and_fill_cache function.

cls->cache.insert(sel, imp, receiver); This is a line of code that we’re already very familiar with. But there’s a judgment here before you do it. If slowPath (objcMsgLogEnabled && implementer) is met, logMessageSend writes some data to/TMP/msgtimeline -%d. And the implemEnter is the current class, so it will execute as long as objcMsgLogEnabled is YES. Now let’s see where objcMsgLogEnabled is assigned. Global search.

There is a default assignment at this pointfalse.

Through global search, ininstrumentObjcMessageSendsFunction of theobjcMsgLogEnabledThe assignment is made. So we can extend this method externally and pass it inYES or NO. I can listen in!

extern void instrumentObjcMessageSends(BOOL flag);
Copy the code

Then run the code.

#import <Foundation/Foundation.h> #import "TPerson.h" extern void instrumentObjcMessageSends(BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { instrumentObjcMessageSends(YES); TPerson * tp = [TPerson alloc]; [tp formatPerson]; instrumentObjcMessageSends(NO); NSLog(@"Hello, World!" ); } return 0; }Copy the code

in/tmp/msgSends-%dThere is a file in the path.

Open the file msgfay-2060.

Analyzing the file, it turns out that the only things we need to focus on are the headers.resolveInstanceMethodThis is what we’re doingOC – Underlying dynamic method resolutionI’ve got it.forwardingTargetForSelectormethodSignatureForSelectordoesNotRecognizeSelectorWe don’t know what it is, so let’s explore and analyze each one.

ForwardingTargetForSelector (fast forwarding process)

Open Command + Shift + O in Xcode to search.

Apple explained in detail here. Let’s implement it in TPerson. Then run

#import "TPerson.h"
#import "TStudent.h"

@implementation TPerson

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s -- %@",__func__ ,NSStringFromSelector(aSelector));
    return [super forwardingTargetForSelector:aSelector];
}
@end
Copy the code
Output: - [TPerson forwardingTargetForSelector:] -- formatPerson - [TPerson formatPerson] : unrecognized selector sent to instance 0x100526860Copy the code

Unrecognized crashed. We also added a formatPerson method to TStudent and implemented the method. So can we forward the message received by TPerson to TStudent? So let’s verify that.

#import "TPerson.h"
#import "TStudent.h"

@implementation TPerson

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s -- %@",__func__ ,NSStringFromSelector(aSelector));
    return [TStudent alloc];
}

@end
Copy the code
Output: - [TPerson forwardingTargetForSelector:] -- formatPerson - [TStudent formatPerson]Copy the code

The program runs normally. It’s not as bloated as the dynamic approach.

So why don’t we implement the formatPerson method in TPerson? Instead of implementing it in TStudent? Is there something wrong with it? Because this is a process through SEL to find imp. The first thing to do is to look for it in the cache via objc_msgSend, if it doesn’t find it. And then I’m going to slow down and look it up by looking at methodList. If you still don’t find it. This method does not exist in memory. Dynamic method resolution can be used to remedy the problem, simply by providing one that can be implemented.

As long as go – (id) forwardingTargetForSelector aSelector: (SEL) this method, we went to TStudent adding method. This ensures the safe operation of TPerson. What if TStudent doesn’t implement this method either. Program will continue to report errors, then walked into the methodSignatureForSelector.

Forward methodSignatureForSelector (slow process)

Next we search methodSignatureForSelector view the official explanation is given.

Next we code implementation, run the program.

#import "TPerson.h"
#import "TStudent.h"

@implementation TPerson

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s -- %@",__func__ ,NSStringFromSelector(aSelector));
    return [TStudent alloc];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s -- %@",__func__ ,NSStringFromSelector(aSelector));
    return [super methodSignatureForSelector:aSelector];
}
@end
Copy the code
Output: - [TPerson forwardingTargetForSelector:] -- formatPerson - [TStudent formatPerson] : unrecognized selector sent to instance 0x100746d00Copy the code

The program crashed. Through crash logs we found that the program did not go to methodSignatureForSelector method. So why is this? Because the formatPerson method is not implemented in TStudent. All in forwardingTargetForSelector method returns [TStudent alloc] wrong. All this place can’t call forwardingTargetForSelector method at the same time. In the official explanation for methodSignatureForSelector, we learned that is need to match forwardInvocation methodSignatureForSelector method: methods used together, and it is in the method for reading. Note: This is where the signature is processed.

#import "TPerson.h"
#import "TStudent.h"

@implementation TPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s -- %@",__func__ ,NSStringFromSelector(aSelector));
    if (aSelector == @selector(formatPerson)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s -- %@ -- %@",__func__ ,anInvocation.target, NSStringFromSelector(anInvocation.selector));
}
@end
Copy the code
Output: - [TPerson methodSignatureForSelector:] -- formatPerson - [TPerson forwardInvocation:] - < TPerson: 0x10074e440> -- formatPerson Hello, World!Copy the code

The program runs normally. This is the slow forwarding process.

Check the forwardInvocation for official explanation

Operate on the forwardInvocation. According to their own needs for relevant processing.

- (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"%s -- %@ -- %@",__func__ ,anInvocation.target, NSStringFromSelector(anInvocation.selector)); TStudent * ts = [TStudent alloc]; if ([self respondsToSelector:anInvocation.selector]) { [anInvocation invoke]; } else if ([ts respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:ts]; } else {NSLog(@" %s -- %@", __func__, NSStringFromSelector(anInvocation. Selector)); }}Copy the code
Output: - [TPerson methodSignatureForSelector:] -- formatPerson - [TPerson forwardInvocation:] - < TPerson: 0x1060474a0> -- formatPerson -[TStudent formatPerson] Hello, World!Copy the code

Hopper disassembly tool

When we run into unrecognized collapse. We can do this by disassembling the executable CoreFoundation.

We can get all the image files through the Image List and find the path to CoreFoundation.

So let’s open CoreFoundation in Hopper.

Then we search globally for __forwarding_prep_0.

Found that __forwarding_prep_0 is called in only one place in CoreFoundation. In the __forwarding_prep_0 method, the ____forwarding___ method is called. Next, let’s take a look at the ____forwarding___ method.

If not implemented forwardingTargetForSelector: method will go loc_64a67 inside. If forwardingTargetForSelector: method return values for hollow will go loc_64a67.

loc_64a67To determine whether there are zombie objects.

Determine whether you can respondmethodSignatureForSelector:Methods. If it responds and the return value is not null, proceedr14Equal to empty walk toloc_64dd7. Obviously this place is not empty.

And then we go down

And then it decides if it can respond_forwardStackInvocation:Methods. We can’t use it in engineering_forwardStackInvocation:This method, obviously this method is an internal level method provided by the system, without exposure to the outside world. If it responds, the call continues down[NSInvocation requiredStackSizeForSignature:r12];Methods. If it cannot respond, an error is reported.

This is why forwardInvocation can be invoked in slow forward flow.

conclusion

Message forwarding flowchart: