Preface: Analysis before the Runtime lookup method in the three stages of the process, divided into the slow and fast search, we also found that when analyzing if find out no method is found, the system is not direct return failure information, but the middle and the other fault-tolerant processing, increases the robustness and expansibility of the program, It provides more room for developers to operate! So let’s move on to what does the system do?

Links to the Runtime messaging series

[I] Analysis of the process of quick search for Runtime messages

[2] Analysis of Runtime message slow search process

[c] Runtime dynamic method resolution and message forwarding

First, dynamic method analysis

1, we through the method to find part of the code, you can get the following source code:


 // No implementation found. Try method resolver once. 011 010

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

Copy the code

The above method is a singleton implementation, indicating that if no method implementation is found, the dynamic method resolution stage will be entered

2. Track the process of method analysis


static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked(a);ASSERT(cls->isRealized());

    runtimeLock.unlock(a);#pragmaMark - If it is not a class method, go into the instantiation method processing
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
    
     #pragmaMark - If it is a class method, go to class method processing
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls); }}#pragmaMark - Enter cache lookup
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

Copy the code

See if the above is passed in a class method or an instance method to follow the different logic, then take the instance method as an example, to trace

3. Dynamic method resolution process (instantiation method as an example)


static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked(a);ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    
    #pragmaMark - Interrupt subsequent execution if no dynamic method resolution is performed
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }
    
     #pragmaMark - After dynamic method parsing, the message is sent here, executing objc_msgSend
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // The cache method is used here, so we won't go there next time
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); .// Some operations to print logs are omitted......}}Copy the code

This is the result of the dynamic resolution process. If the dynamic resolution process was performed on the method, objc_msgSend is re-executed. This is why the + resolveInstanceMethod method in the custom class is executed twice. This is the end of dynamic resolution, if there is no dynamic method resolution processing operation, proceed to the next step, we continue to track the source code.

4. Dynamic method resolution unrealized follow-up process analysis

In order to know the execution process of the program after the dynamic method resolution, let’s create a new demo project for analysis.

Add the corresponding method other where the method is dynamically resolved, and you can see the result of the program:


202107 -- 04 20:50:59.009403+0800Method call demo[1611:5853209] -[Person other]
Program ended with exit code: 0

Copy the code

Instead of adding dynamic parsing to the program, we get the program crash information, using the bt command in the LLDB command, we get the stack information:


(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007fff2043f92e libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x000000010038de79 libsystem_pthread.dylib`pthread_kill + 263
    frame #2: 0x00007fff203c3411 libsystem_c.dylib`abort + 120
    frame #3: 0x00007fff20431ef2 libc++abi.dylib`abort_message + 241
    frame #4: 0x00007fff204235fd libc++abi.dylib`demangling_terminate_handler() + 266
    frame #5: 0x00007fff2031c58d libobjc.A.dylib`_objc_terminate() + 96
    frame #6: 0x00007fff20431307 libc++abi.dylib`std::__terminate(void(*) ()) +8
    frame #7: 0x00007fff20433beb libc++abi.dylib`__cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*) + 27
    frame #8: 0x00007fff20433bb2 libc++abi.dylib`__cxa_throw + 116
    frame #9: 0x00007fff20319ec0 libobjc.A.dylib`objc_exception_throw + 350
    frame #10: 0x00007fff2066438d CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 132
    frame #11: 0x00007fff2054990b CoreFoundation`___forwarding___ + 1448
    frame #12: 0x00007fff205492d8 CoreFoundation`_CF_forwarding_prep_0 + 120
  * frame #13: 0x0000000100003e86Method calls demo 'main(argc=1, argv=0x00007ffeefbff468) at main.m:14:9
    frame #14: 0x00007fff20489f5d libdyld.dylib`start + 1
    frame #15: 0x00007fff20489f5d libdyld.dylib`start + 1

Copy the code

Analyzing stack information, from bottom to top analysis, we get ‘CF_forwarding_prep_0’, ‘__ forwarding__’, ‘doesNotRecognizeSelector’ methods.

These methods are in the CoreFoundation library. Try to find them in the Runtime source code.

Finally, we found the relevant source code through the doesNotRecognizeSelector method, but it was still not helpful to the analysis program, so we had to disassemble the CoreFoundation library (using the tool IDA64).

We write all the methods we find into the implementation of Person, and then look at the method calls and the order in which they are called, as follows:

So, we know the dynamic message resolution after the implementation of the process, let’s talk about such a process ———— message forwarding process!

2. Message forwarding

As we all know, when executing resolveInstanceMethod, there is no processing logic associated with it and it enters another method: ForwardingTargetForSelector, through the method name we can know is the need to pass in a processing method of the object, namely the Person class test method is not implemented, we will pass in a other classes to deal with the test method. Here’s how we’ll try:

1, forwardingTargetForSelector method

Here we found that introduced into an implementation of a test method of the object after the Man, forwardingTargetForSelector calls the Man inside the test method, the program runs and not collapse, and the latter method did not go away. If we don’t realize forwardingTargetForSelector, continue to explore methodSignatureForSelector next method.

2, methodSignatureForSelector method

As you can see, in the next method, we pass in the required method signature (as declared -(void)test;) . After performing methodSignatureForSelector method, repeated to call resolveInstanceMethod message resolution, still find messages is not successful, behind and go walk forwardInvocation method.

3. ForwardInvocation

Objc_msgSend (‘ Man ‘, ‘test’); objc_msgSend (‘ Man ‘, ‘Man’, ‘test’);

DoesNotRecognizeSelector method

Reason: ‘-[xxxClass XXX]: unrecognized selector sent to instance 0x100614280’

Summary of Runtime message sending

1, forwardingTargetForSelector and methodSignatureForSelector forwardInvocation can implement method of forwarding process, Why not use forwardingTargetForSelector directly?

Analysis and understanding: While forward forward fast and slow process can meet, and like forwardingTargetForSelector efficiency and convenience is high, but the demand for complex scenarios, such as monitoring unrealized method calls, the collapse of the special processing, etc., in forwardInvocation processing more convenient!

2. Typical application scenarios of message sending

For example, anti-crash processing, many scenarios, methods forget to write implementation, especially at the same time there are a lot of classes, we go looking for positioning, not so convenient, through the AOP section programming way, collect error information, can be used for log reporting, conducive to development and maintenance.

3. Flow chart of Runtime message sending (message sending, dynamic method resolution and message forwarding process)

  • Present my manual flowchart (message sending and dynamic method parsing)

  • Offer my manual flow chart (message forwarding)

The above is the summary of the Runtime message sending, dynamic method parsing and message forwarding process. If you have time later, you will make some updates and improvements to it. If you like, welcome to like and collect! If you have any questions, welcome to ask, learn and progress together!