Code word is not easy, ask a wave of thumbs up, attention. Thank you!!

preface

OC method lookup if the fast lookup process and slow lookup process do not find the corresponding IMP, and dynamic method resolution does not dynamically add the corresponding IMP, will enter the message forwarding process. Having already analyzed the objc_msgSend fast lookup process, the objc_msgSend slow lookup process, and the objc_msgSend dynamic method resolution of the iOS Runtime, this article explores the message forwarding process.

The preparatory work

  • Objc4-818.2 – the source code.
  • CoreFoundation source
  • Disassembly toolHopperandida.

1: exploring the message forwarding process

1.1: Exploration of the message forwarding source process

// Only part of the code is cut because of length
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    // Specify imp for message forwarding
    constIMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; .for (...) {
        ...
        if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
            // No implementation found, and method resolver didn't help.
            // Use forwarding.
            (CLS ->supercls->nil) (CLS ->supercls->nil
            // If the dynamic method resolution does not work, the message is forwarded
            imp = forward_imp;
            break; }... }... }Copy the code
  • According to thelookUpImpOrForwardThe source code of the function is known for message forwardingimpThe default is_objc_msgForward_impcache, global search, inC++Source code did not find the relevant implementation, according to the experience of exploring before, into the assembly fileobjc-msg-arm64.sFind the relevant source code__objc_msgForward_impcache.

  • __objc_msgForward_impcachejump__objc_msgForwardAnd then it’s evaluated__objc_forward_handlerdepositp17, and finally callp17. Global search__objc_forward_handler, the implementation part was not found, as a rule of thumb remove the underline search, inC++fileobjc-runtime.mmFound the relevant source code.

  • Void *_objc_forward_handler = (void*)objc_defaultForwardHandler The object c_defaultForwardHandler function pointer contains the classic unrecognized Selector sent to XXX error.

  • Recognized selector sent to; recognized selector sent to; recognized selector sent to; recognized selector sent to; recognized selector sent to; recognized selector sent to Comments are also shown to be replaced by CoreFoundation.

  • Until you notice the objc_setForwardHandler function, and interrupt the output of the FWD argument passed in, The program runs and assigns _objc_forward_handler to the CoreFoundation function _CF_forwarding_prep_0.

1.2: Case verification

Create SDSingleDog class, declare -girlfriend and +getMarried, neither of them can be implemented, run code, single dog starts looking for girlfriend, can’t find, he will break down.

@interface SDSingleDog : NSObject

- (void)girlfriend;

+ (void)getMarried;

@end

@implementation SDSingleDog

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        SDSingleDog *singleDog = [SDSingleDog alloc];
        [singleDog girlfriend];
        
        NSLog(@"Hello, World!");
    }
    return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 12 16:30:02.439981+0800 KCObjcBuild[4559:142606] -[SDSingleDog girlfriend]: unrecognized selector sent to instance 0x1018adac0
Copy the code

Running result diagram:

Unrecognized Selector sent to XXX

  • Before the exception is thrown_CF_forwarding_prep_0Functions and___forwarding___Function is called according toObjc_msgSend dynamic method resolution for iOS RuntimeGuess, this should be the relevant process triggered by message forwarding.
  • _CF_forwarding_prep_0The function,___forwarding___Functions anddoesNotRecognizeSelectorMethods forCoreFoundationLibrary source code.

Since the CoreFoundation source code is not fully open source, Above the iOS objc_msgSend dynamic method the runtime resolution we already know that by means of auxiliary debug log message forwarding relevant methods forwardingTargetForSelector: and methodSignatureForSelector: We will expand the analysis of message forwarding from here.

2: fast forwarding

2.1: Fast forwarding API analysis

Through log auxiliary function know forwardingTargetForSelector: method, but the default implementation simply returns nil in the source code.

In order to quickly understand this method, at the same time hold down the command + shift + 0, evocation development documents, search forwardingTargetForSelector: method, to check the related documents.

According to development documentation forwardingTargetForSelector: the method is to give the unrecognized message returns a shall, first of all, pointing to the object, this object to receive the news (is nil and the self), if the object cannot handle, will also be recursive search, the parent class Check to see if the parent on the inheritance chain can handle it.

2.2: Case verification fast forwarding

Declare the SDSingleDog and XJStudent classes, SDSingleDog states the – (void)girlfriend method, but does not implement it, XJStudent implements the – (void)girlfriend method, The – (void)girlfriend method is called from the object of the SDSingleDog class in the main function (SDSingleDog and XJStudent may not be inherited).

@implementation SDSingleDog

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (@selector(girlfriend) == aSelector) {
        return [[XJStudent alloc]init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

@implementation XJStudent

- (void)girlfriend
{
    NSLog(@"%s, The student have girlfriend", __func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SDSingleDog *singleDog = [SDSingleDog alloc];
        [singleDog girlfriend];
    }
    return 0; } * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 13 00:25:55.525656+0800 KCObjcBuild[4424:102160] -[XJStudent girlfriend], The student have girlfriend
Copy the code
  • The message was forwarded successfullyXJStudentClass.SDSingleDogClasses andXJStudentClass, but after the redirection, the program runs normally.

This method gives the object the opportunity to redirect the unknown messages sent to it before the more expensive regular forwardInvocation: take over. This is useful when you just want to redirect the message to another object, and can be an order of magnitude faster than regular forwarding, so it is also known as fast forwarding.

3: indicates slow forwarding

3.1: Slow forwarding API analysis

If the rapid message forwarding is not implemented, then the slow (also called regular) invocation will be used, again holding command + shift + 0 and calling the development document and looking for the forwardInvocation.

According to the document to the response object itself can’t identify method, also have to rewrite methodSignatureForSelector: besides forwardInvocation:. Forwarding message mechanism using from methodSignatureForSelector: the information to create the NSInvocation object is forwarding. Rewrite methodSignatureForSelector: method must provide the appropriate method for the given selector signature NSMethodSignature, can be set in advance a method signature, also can be to another object request a method signature. If methodSignatureForSelector: method returns nil, wouldn’t call forwardInvocation:.

3.2: Case verification of slow forwarding

SDSingleDog class methodSignatureForSelector: return to/super methodSignatureForSelector: aSelector method directly, namely, nil.

@implementation SDSingleDog

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"@ ___ ___ %", anInvocation); } @ the end * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 13 11:51:04.491402+0800 KCObjcBuild[1693:50929] -[SDSingleDog girlfriend]: unrecognized selector sent to instance 0x101931220
Copy the code
  • methodSignatureForSelector:Method returns directlynil, the slow forwarding terminates, the program crashes and is thrownunrecognized selector sent to xxxThe exception.
@implementation SDSingleDog

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (@selector(girlfriend) == aSelector) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"___target = %@, sel = %@, methodSignature = %@", anInvocation.target, NSStringFromSelector(anInvocation.selector), anInvocation.methodSignature); } @ the end * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 13 14:49:32.360478+0800 KCObjcBuild[2748:93742] ___target = <SDSingleDog: 0x1058a8360>, sel = girlfriend, methodSignature = <NSMethodSignature: 0xb78f2e4cb57ba1c7>
Copy the code
  • methodSignatureForSelector:Method returns as neededNSMethodSignatureObject, will be calledforwardInvocation:Method for message forwarding,anInvocationParameterNSMethodSignatureMethod signature, which was forwardedselector, and method receiverstarget. At this point, the program no longer crashes and can forward the message as needed. If the forwarded object also fails to respond to the message, it can use its ownforwardInvocation:Method to continue forwarding.
@implementation SDSingleDog

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (@selector(girlfriend) == aSelector) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"___target = %@, sel = %@, methodSignature = %@", anInvocation.target, NSStringFromSelector(anInvocation.selector), anInvocation.methodSignature);
    SEL aSelector = [anInvocation selector];
    if (aSelector == @selector(girlfriend)) {
        XJBoy *boy = [[XJBoy alloc]init];
        anInvocation.target = boy;
        [anInvocation invoke];
    }
    
}

@end

@implementation XJBoy

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (@selector(girlfriend) == aSelector) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"___target = %@, sel = %@, methodSignature = %@", anInvocation.target, NSStringFromSelector(anInvocation.selector), anInvocation.methodSignature);
    SEL aSelector = [anInvocation selector];
    if (aSelector == @selector(girlfriend)) {
        XJPerson *person = [[XJPerson alloc]init];
        anInvocation.target = person;
        anInvocation.selector = @selector(loveEveryone);
        [anInvocation invoke];
    }
}

@end

@implementation XJPerson

- (void)loveEveryone
{
    NSLog(@"%s, Everyone loves me, I love everyone", __func__); } @ the end * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * *202107 -- 13 15:22:01.636226+0800 KCObjcBuild[3262:114582] ___target = <SDSingleDog: 0x10090df20>, sel = girlfriend, methodSignature = <NSMethodSignature: 0x9f2189f206557a37>
202107 -- 13 15:22:01.637765+0800 KCObjcBuild[3262:114582] ___target = <XJBoy: 0x10071a760>, sel = girlfriend, methodSignature = <NSMethodSignature: 0x9f2189f206557a37>
202107 -- 13 15:22:01.637832+0800 KCObjcBuild[3262:114582] -[XJPerson loveEveryone], Everyone loves me, I love everyone
Copy the code
  • SDSingleDogUnable to respond togirlfriendMessage, forwards the message toXJBoy.XJBoyCan’t respond either. Continue forwarding toXJPersonAnd willselectorChange toloveEveryoneAnd finally theXJPersonThe corresponding method is found and the message is forwarded successfully.

ForwardInvocation: The method is like the unrecognized invocation center, it can specify a new receiver for an unrecognized message, or translate it into another message, or just “eat” the message, so it doesn’t crash even if it’s not processed.

4: Summary and flowchart of message forwarding

4.1: Summary of message forwarding

  • Quick forward: YesforwardingTargetForSelector:Method returns an object that should be pointed to first for an unidentified message to receivenilandself), performs the lookup process of the object (method fast lookup -> method slow lookup, also recursively until the parent classnil), if it is not found, it enters the slow forwarding process.
  • Slow forwarding: YesmethodSignatureForSelector:Methods andforwardInvocation:Methods are implemented together ifmethodSignatureForSelector:Method returnsnil, the slow search process ends; If the correct method signature is returned,forwardInvocation:Method can be forwarded, new message recipients can be specified, and changes can be madeselector, and the specified new receiver can forward again.

4.2: Message forwarding flowchart

5: Disassembly explorationCoreFoundation

As previously analyzed, CoreFoundation is not fully open source and the source code for message forwarding is not visible. To explore the corresponding process, we use disassembly to explore it.

Select a CoreFoundation executable file, create a new arm64 architecture project, click a break point to break it, then use the LLDB image list command to output the path of all the image files, find the CoreFoundation corresponding path, open Finder, At the same time, hold command + shift + G, call out to folder, paste the path of the CoreFoundation into it and press enter to get it.

5.1: HopperexploreCoreFoundation

Open the CoreFoundation executable with Hopper and search for the _forwarding_prep_0 function. There is only one in the global file, and the ___forwarding___ function is called as shown in the crash message. Double-click to enter ___forwarding___.

Fast forwarding process

  • ifforwardingTargetForSelector:Method not implemented, jumploc_64a67Process.
  • ifforwardingTargetForSelector:Method returnsnilorselfJump,loc_64a67Process.

Slow forwarding process

  • ifmethodSignatureForSelector:Method not implemented, jumploc_64dd7Process.
  • ifmethodSignatureForSelector:Method returnsnilJump,loc_64e3cProcess.
  • ifmethodSignatureForSelector:Method returns the method signature and continues the process.

  • loc_64dd7Process: direct output error, and then jumploc_64e3cProcess.
  • loc_64e3cProcess: calldoesNotRecognizeSelector:Methods.

  • doesNotRecognizeSelector:Method outputs an error message based on whether the message receiver is a class or an object, and then throws an exception.

  • ifmethodSignatureForSelector:Method returns a method signature, based on which it is generatedNSInvocationClass, and then callforwardInvocation:Methods.

5.2: idaexploreCoreFoundation

Open the CoreFoundation executable file with IDA, right-click on the Function Name menu bar on the left, select Quick Filter to search for the _CF_forwarding_prep_0 Function, and click F5 to convert it to pseudo-code mode to view. It is found that there is only one global Function. The ___forwarding___ function is called as shown in the crash message. Double-click to enter ___forwarding___.

Fast forwarding process

  • forwardingTargetForSelector:Method, not implemented, returnnilorselfJump Indicates the slow forwarding process. Otherwise, fast forwarding is performed.

Slow forwarding process

  • methodSignatureForSelector:Not implemented, error calldoesNotRecognizeSelector:Method throws an exception.

  • methodSignatureForSelector:Method returnsnil, you jumpLABEL_50calldoesNotRecognizeSelector:Method throws an exception.
  • ifmethodSignatureForSelector:Method returns a method signature, based on which it is generatedNSInvocationClass, and then callforwardInvocation:Methods.

5.3: Summary of disassembly

  • According to two disassembly tools forCoreFoundationAnd found that the message forwarding process was roughly the same as we guessed.
  • Disassembly is a great way to explore when we don’t know the underlying implementation and don’t have the relevant source code.

6:

  • Method search is a complex and tedious process, there are fast search, slow search, Apple for the stability of the program, but also made a lot of efforts, such as dynamic method resolution, fast message forwarding, slow message forwarding, etc., can be said to be well-meaning. aboutobjc_msgSendThis is basically the end of the exploration.