IOS bottom layer exploration – message forwarding

Time:2021-6-7

IOS bottom Exploration Series

  • IOS bottom layer exploration – alloc & init
  • Exploration of IOS bottom layer – calloc and ISA
  • IOS bottom layer exploration – class
  • IOS bottom layer exploration cache_ t
  • IOS bottom layer exploration method
  • IOS bottom layer exploration – message search
  • IOS bottom layer exploration – message forwarding

1、 Dynamic method analysis process analysis

We analyzed it in the previous chapter “message search”Dynamic method analysis, in order to better grasp the specific process, we next directly source tracking.

Let’s go first_class_resolveMethodThe source code of this method is as follows:

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]

        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

The general process is as follows:

  • Judge whether it is a metaclass to parse
  • If it is not a metaclass, the_class_resolveInstanceMethodDynamic analysis of object method
  • If it is a metaclass, call_class_resolveClassMethodDynamic analysis of class methods
  • After dynamic parsing of class method, query againclsInimpIf it is not found, the object method will be dynamically parsed once

1.1 dynamic analysis of object method

Let’s first analyze the dynamic parsing of object methods. Let’s go directly to_class_resolveInstanceMethodMethod Department:

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

The general process is as follows:

  • Check if it is implemented+(BOOL)resolveInstanceMethod:(SEL)selClass method, if not implemented, directly returns (viacls->ISA()Is to get metaclasses, because class methods are object methods stored on metaclasses.)
  • If the current implementation+(BOOL)resolveInstanceMethod:(SEL)selClass methodobjc_msgSendCall the method manually
  • After the call, query againclsInimp
  • IfimpIf it is found, the successful log of dynamic parsing object method will be output
  • IfimpIf not, the output is implemented+(BOOL)resolveInstanceMethod:(SEL)sel, and returnedYES, but not foundimpLog of

IOS bottom layer exploration - message forwarding

Dynamic analysis of class 1.2 methods

Next, we analyze the dynamic parsing of class methods. Let’s go directly to_class_resolveClassMethodMethod Department:

static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

The general process is as follows:

  • Whether the assertion is a metaclass, if not, exit directly
  • Check if it is implemented+(BOOL)resolveClassMethod:(SEL)selClass method, if not implemented, directly returns (viacls-Because of the current situationclsIs metaclass, because class methods are object methods stored on metaclasses.)
  • If the current implementation+(BOOL)resolveClassMethod:(SEL)selClass methodobjc_msgSendCall this class method manually. Note that this method is different from the dynamic object parsing methodFind classes through metaclasses and objectsThat is to say_class_getNonMetaClass
  • After the call, query againclsInimp
  • IfimpIf it is found, the successful log of dynamic parsing object method will be output
  • IfimpIf not, the output is implemented+(BOOL)resolveClassMethod:(SEL)sel, and returnedYES, but not foundimpLog of

IOS bottom layer exploration - message forwarding

Here’s a note. If we take the example above as an exampleobjc_getMetaClass("LGPerson")change intoselfTry, it will lead to+(BOOL)resolveInstanceMethod:(SEL)selMethod is called, but the problem is thatclass_getMethodImplementationMethod, which internally calls the_class_resolveMethodMethods, and ourclsIt is said thatselfSo I’ll go again+(BOOL)resolveInstanceMethod:(SEL)sel
IOS bottom layer exploration - message forwarding

1.3 specialNSObjectObject method dynamic parsing

Let’s focus again_class_resolveMethodIn method, ifclsIs a metaclass, that is to say, the dynamic parsing of class methods, there are the following source code:

_ class_ resolveClassMethod(cls, sel, inst); //  It has been processed
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            //Object method resolution
            _class_resolveInstanceMethod(cls, sel, inst);
        }

about_class_resolveClassMethodThere must be no problem with the execution of. But why do we need to do another object method parsing after judging if the dynamic parsing fails? At this time, we need a classic oneisaHere we go

IOS bottom layer exploration - message forwarding

From this flowchart, we can see that metaclasses eventually inherit fromRoot metaclass, andRoot metaclassAnd inherited fromNSObjectIn other wordsRoot metaclassClass methods stored in are equivalent toNSObjectObject methods stored in. And the system is executinglookUpImpOrNilRecursively looks up the method list of the parent class of the metaclass. However, because metaclasses and root metaclasses are automatically generated by the system, we can’t write them directlyNSObjectWe can use classification(Category)To achieveUnified dynamic parsing of class methods, but only if the class itself is not implementedresolveClassMethodmethod:

IOS bottom layer exploration - message forwarding

That explains why_class_resolveClassMethodWhy is there one more step in the process of object method parsing.

2、 Fast process of message forwarding

If we don’t do dynamic method parsing, what will the message lookup process come to next?

// No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

according tolookUpImpOrForwardSource code we can see that when the dynamic parsing is not successful, it will directly return a_objc_msgForward_impcache. Let’s try to search it and locate itobjc-msg-arm64.sSource code department:

STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b    __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    
    ENTRY __objc_msgForward

    adrp    x17, [email protected]
    ldr    p17, [x17, [email protected]]
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward

You can see in the__objc_msgForward_impcacheThe interior will jump to__objc_msgForward, and__objc_msgForwardInternally, we don’t get useful information. Is the clue broken at this time? Let’s discuss the previous process. If we find itimp, will fill the cache and print the log, we might as well find the printed log file to see if there will be the content we need.

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 ? '+' : '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}

Here we can clearly see that the storage location of the log file has been named

IOS bottom layer exploration - message forwarding

Here’s another point to note:

IOS bottom layer exploration - message forwarding

Only whenobjcMsgLogEnabledThis value istrueThe log will be output only when the value is set. We directly search the place where the value appears

IOS bottom layer exploration - message forwarding

IOS bottom layer exploration - message forwarding

Obviously, by callinginstrumentObjcMessageSendsCan be used to achieve printing on and off. We can simply test:

IOS bottom layer exploration - message forwarding

Let’s run it, and then we come to/private/tmpUnder the table of contents:

IOS bottom layer exploration - message forwarding

We open this file:

IOS bottom layer exploration - message forwarding

We see the familiarresolveInstanceMethodBut after that, there are two ways we haven’t explored before:forwardingTargetForSelectorandmethodSignatureForSelector. And then there will bedoesNotRecognizeSelectorMethod, at this timeXcodeThe console is printed as follows:

IOS bottom layer exploration - message forwarding

We can see that___forwarding___happen toCoreFoundationInside the frame. We still have the same rules,Subject to official documents, check it outforwardingTargetForSelectorandmethodSignatureForSelector

FirstforwardingTargetForSelector:

IOS bottom layer exploration - message forwarding

forwardingTargetForSelectorThe official definition of return is not foundIMPThe message is first directed to the object, which can be realized in this methodCivet for PrinceIt’s not that I can’t find itIMPWhy don’t I just leave this message to someone else to deal with? We speak directly in Code:

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

Here we go straight back[LGTeacher alloc]Let’s run it

IOS bottom layer exploration - message forwarding

Perfect, we’re rightLGStudentInstance object sendingsaySomethingMessage, the result is finally byLGTeacherIn response to this message. aboutforwardingTargetForSelectorApple also gave a few tips:

If an object implements (or inherits) this method, and returns a non-nil (and non-self) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously if you return self from this method, the code would just fall into an infinite loop.)
If an object implements or inherits the method, then returns a non null (non null) valueself)Then the return value will be treated as a new message recipient object, and the message will be forwarded to the object( If you return in this methodselfAnd then, obviously, there will be oneDead cycle)。

If you implement this method in a non-root class, if your class has nothing to return for the given selector then you should return the result of invoking super’s implementation.
If you implement this method in a non base class and the class has nothing to return, you need to return the implementation of the parent class. that isreturn [super forwardingTargetForSelector:aSelector];

This method gives an object a chance to redirect an unknown message sent to it before the much more expensive forwardInvocation: machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding.
This method gives the object the opportunity to work in more expensive environmentsforwardInvocation:Redirect unknown messages sent to the machine before it takes over. This is useful when you just want to redirect the message to another object, and it’s an order of magnitude faster than normal forwarding. The goal of forwarding is to captureNSInvocationOr in the case of manipulating parameters or return values in the forwarding process, this function is useless.

Through the above official document definition, we can sort out the following ideas:

  • forwardingTargetForSelectorIt is a fast message forwarding process, which allows other objects to respond to unknown messages directly.
  • forwardingTargetForSelectorCannot returnselfOtherwise, it will fall into a dead loop because it returnsselfGo back to the current instance object and go through the message lookup process. It’s obvious that you’ll come backforwardingTargetForSelector
  • forwardingTargetForSelectorIt is suitable for forwarding messages to other objects that can respond to unknown messages. What does it mean? The final returned content must be consistent with the parameters and return values of the message to be searched. If you want to be inconsistent, you need to go through other processes.

3、 Slow flow of message forwarding

As mentioned above, if the content to be returned is inconsistent with the parameters and return values of the message to be searched, other processes need to be followed. So what process is it? Let’s take a look at another method just nowmethodSignatureForSelectorOfficial document of:

IOS bottom layer exploration - message forwarding

The official definition ismethodSignatureForSelectorReturn aNSMethodSignatureMethod signature object that contains the description of the method identified by the given selector.

This method is used in the implementation of protocols. This method is also used in situations where an NSInvocation object must be created, such as during message forwarding. If your object maintains a delegate or is capable of handling messages that it does not directly implement, you should override this method to return an appropriate method signature.
This method is used to implement the protocol. At the same time, when forwarding messages, theNSInvocationThis method is also used in the case of object. If your object maintains a delegate or can handle messages that it does not implement directly, you should override this method to return the appropriate method signature.

At the end of the document, we can see a file calledforwardInvocation:How to do it

IOS bottom layer exploration - message forwarding

We come to the documentation of this method:

IOS bottom layer exploration - message forwarding

To respond to methods that your object does not itself recognize, you must override methodSignatureForSelector: in addition to forwardInvocation:. The mechanism for forwarding messages uses information obtained from methodSignatureForSelector: to create the NSInvocation object to be forwarded. Your overriding method must provide an appropriate method signature for the given selector, either by pre formulating one or by asking another object for one.
To respond to methods that are not recognized by the object itself, exceptforwardInvocation:In addition, it must be rewrittenmethodSignatureForSelector:. The mechanism of forwarding messages uses themethodSignatureForSelector:To create a message to forwardNSInvocationObject. Your overriding method must provide an appropriate method signature for a given selector by prescribing a formula in advance or by requiring another object to provide a method signature.

obviously,methodSignatureForSelectorandforwardInvocationThey don’t exist in isolation, they need to appear together. Let’s talk directly from the code:

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


- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s ",__func__);

   SEL aSelector = [anInvocation selector];

   if ([[LGTeacher alloc] respondsToSelector:aSelector])
       [anInvocation invokeWithTarget:[LGTeacher alloc]];
   else
       [super forwardInvocation:anInvocation];
}

Then view the print results:

IOS bottom layer exploration - message forwarding

As you can see, first camemethodSignatureForSelectorAnd then it cameforwardInvocationAnd finallysaySomethingThe message was found.

aboutforwardInvocationThere are also several points to note:

  • forwardInvocationThe method has two tasks

    • Find the responseinInvocationObject of the message encoded in. This object does not have to be the same for all messages.
    • useanInvocationSend a message to the object.anInvocationThe results will be saved and the runtime system will extract the results and pass them to the original sender.
  • forwardInvocationThe implementation of method can not only forward messages.forwardInvocationYou can also, for example, combine code that responds to a variety of different messages, avoiding the hassle of having to write separate methods for each selector.forwardInvocationMethod may also involve several other objects in the response to a given message, rather than forwarding them to just one object.
  • NSObjectOfforwardInvocationImplementation: only callsDosnotrecognizeselector: method, which does not forward any messages. Therefore, if you choose not to implementForward invocation ‘, sending an unrecognized message to an object will throw an exception.

So far, we have explored the slow process of message forwarding.

4、 Message forwarding flow chart

From dynamic message parsing to fast forwarding process and then to slow forwarding process, we can summarize the following flow chart:

IOS bottom layer exploration - message forwarding

5、 Summary

We start fromobjc_msgSendAt the beginning, we explored the process after the message is sent, which is very important for us to understandiOSThe bottom floor helps a lot. Of course, limited to the level of the author, the process of exploration may have some flaws. Let’s briefly summarize:

  • Dynamic method analysis is divided into two partsObject method dynamic parsingandDynamic analysis of class methods

    • Object method dynamic parsingMessage sender implementation required+(BOOL)resolveInstanceMethod:(SEL)selmethod
    • Dynamic analysis of class methodsMessage sender implementation required+(BOOL)resolveClassMethod:(SEL)selmethod
  • If dynamic method parsing fails, it will enter the message forwarding process
  • Message forwarding is divided into two processes: fast forwarding and slow forwarding
  • The realization of fast forwarding is very importantforwardingTargetForSelectorTo enable other objects to respond to the message to be found
  • What is the implementation of slow forwardingmethodSignatureForSelectorandforwardInvocationTo provide more fine-grained control, first return the method signature toRuntime, and then letanInvocationTo send the message to the provided object, and finallyRuntimeThe result is extracted and then passed to the original message sender.

iOSNow that we’re in Chapter 7, we’ll start from the bottomappLoad, start to explore, explorecold bootandHot start, anddyldHow it works.