In the previous two articles, objc_msgSend Fast method Lookup and objc_msgSend Slow Method lookup, we explored the essence of function calling, namely message sending: objc_msgSend, and tracked the source code to learn the process of method lookup. What if the fast search and the slow search do not find the method?

1

According to the first two articles, two questions are raised:

  1. forward_impWhat is?
  2. If the method cannot be found, how to remedy it?

1. What is forward_IMP?

As explained in the previous article, imp is set to forward_IMP by default if the method is not found, that is, if superclass finds nil along the way and still does not find it. So what is forward_IMP? In the first line of the lookUpImpOrForward method of the slow lookup process, forward_IMP is assigned:

const IMP forward_imp = (IMP)_objc_msgForward_impcache;
Copy the code

__objc_msgForward_impcache; find the implementation of the method in objc_msg_arm64.s:

        STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
Copy the code

(objc_defaultForwardHandler) (objc_defaultForwardHandler) (objc_defaultForwardHandler)

// Default forward handler halts the process. __attribute__((noreturn, cold)) void objc_defaultForwardHandler(id self, _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p " "(no message forward handler is installed)", class_isMetaClass(object_getClass(self)) ? '+' : '-', object_getClassName(self), sel_getName(sel), self); } void *_objc_forward_handler = (void*)objc_defaultForwardHandler;Copy the code

This sounds familiar, but it’s a common error we encounter in everyday development: a function that is not implemented, or a program that crashes when running.

2. If the solution is not found, how to fix it?

  • Dynamic method resolution: If the slow search process is not found, a dynamic method resolution will be executed.
  • forward: If the dynamic method resolution still does not find an implementation, the message is forwarded. Message forwarding is divided into:Fast and slow message forwarding.

2. Dynamic method resolution

In the slow method lookup in the last article, when superclass = nil, you break out of the loop, and then you’re given another chance, dynamic method resolution, to redefine your method implementation.

1. Dynamic method resolution source analysis

In lookUpImpOrForward we have the following code, which is a dynamic method resolution entry:

    // No implementation found. Try method resolver once.

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

Slowpath (Behavior & LOOKUP_RESOLVER) can be interpreted as an on/off valve to ensure that dynamic method decisions are only executed once! Enter the resolveMethod_locked method to learn more about the dynamic method resolution process.

static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); // If (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] resolveInstanceMethod(inst, sel, cls); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] resolveClassMethod(inst, sel, cls); if (! LookUpImpOrNilTryCache (inst, sel, CLS)) {// Because the class is also a metaclass object resolveInstanceMethod(inst, sel, CLS); } // chances are that calling the resolver have populated the cache // so attempt to use it // If the method resolves to point to another method, To find out the process continued to walk return lookUpImpOrForwardTryCache (inst, sel, CLS, behaviors); }Copy the code

Process analysis:

  1. judgeclsThe type of;
  2. If it is a class, executeInstance methodsDynamic resolution ofresolveInstanceMethodMethods.
  3. If it is a metaclass, executeClass methodDynamic resolution ofresolveClassMethodMethods. If the instance method is not found in the metaclass or is empty, the dynamic method resolution is made in the metaclass instance methodresolveInstanceMethodIn the search. Why is that?Because class methods are stored as object methods in the metaclass, the metaclass's instance object resolution methods need to be executed. That is, a class is an instance object of a metaclass.
  4. If the method resolution points the implementation of the method elsewhere, the last line of execution continueslookUpImpOrForwardTryCacheMethod, goes through the method lookup process, and returnsimp.

2. ResolveInstanceMethod source analysis

Object method dynamic method resolution calls the resolveInstanceMethod method. The source code is as follows:

Process analysis:

  1. A place: Performs a slow method lookup to determine if the class is implementedresolveInstanceMethodMethods; If not found, return directly;
  2. B: If found, the message is sent and executedresolveInstanceMethodMethods;
  3. C place: Searches the method again, that is, passes_lookUpImpTryCacheMethods into thelookUpImpOrForwardDo a slow method lookup.

It's very confusing. What do you do at each step? The following cases are used to explore and analyze.

3. Preliminary case exploration

Add two methods to the declaration of class LGPerson, -(void)sayHello; And – (void) sayHello1; ; In the class implementation, override the resolveInstanceMethod method and implement the method -(void)sayHello1, which is not implemented.

@interface LGPerson : NSObject -(void)sayHello; -(void)sayHello1; + (void)say666; + (void)say6661; @end @implementation LGPerson - (void)sayHello1{ NSLog(@"sayHello1 %s", __func__); } + (void)say6661{ NSLog(@"say6661 %s", __func__); + (BOOL)resolveInstanceMethod (SEL) SEL {NSLog(@" give you a chance..."); ); // Do nothing return 0; } @endCopy the code

Case running result:

ResolveInstanceMethod (resolveInstanceMethod) (resolveInstanceMethod) (resolveInstanceMethod) Why is that? Let’s do code trace debugging.

  1. Run the above example again to filter out what we need to study, which is that the LGPerson object calls the sayHello method and enters the dynamic method resolution method resolveInstanceMethod. See below:

  2. Determine if CLS, LGPerson, implements the resolveInstanceMethod class method. See below:

  3. Check whether resolveInstanceMethod is implemented in the metaclase. The path is: lookUpImpOrNilTryCache -> _lookUpImpTryCache -> lookUpImpOrForward. See the following figure:

  4. In the method list, the resolveInstanceMethod method is successfully found and the cache is inserted.

  5. If not found, this will be returned directly, that is, did not take advantage of the opportunity, directly returned! If found, a resolveInstanceMethod message is sent, executing the resolveInstanceMethod method.

  6. After the message is sent, the sayHello method will be searched again, but it still cannot be found! Because LGPerson implements resolveInstanceMethod, but it doesn’t do anything!

  7. ResolveInstanceMethod execution is finished, return to resolveMethod_locked process, called lookUpImpOrForwardTryCache method to find the again.

  8. In lookUpImpOrForwardTryCache, still find sayHello, at this time will return from the cache forward_imp, namely for message forwarding.

  9. End of dynamic method resolution process, only in this case, although the implementation of dynamic method resolution, but there is nothing to do, into the message forwarding, the final result of the error!

Conclusion: Through the above case, the process of dynamic method resolution is clarified. But in the actual development process, we will certainly seize the opportunity to handle the error. Let’s modify the case to point the sayHello method to another method.

4. In-depth exploration of cases

It’s still the case above, but let’s take the opportunity to add a method to the class whose sel is still sayHello, but whose corresponding method implements IMP as sayHello1.

+ (BOOL)resolveInstanceMethod (SEL) SEL {NSLog(@" give you a chance..." ); if (sel == @selector(sayHello)) { IMP imp = class_getMethodImplementation(self, @selector(sayHello1)); Method method = class_getInstanceMethod(self, @selector(sayHello1)); class_addMethod(self, sel, imp, method_getTypeEncoding(method)); return NO; } return [super resolveInstanceMethod:sel]; }Copy the code

Running the above code, what’s different this time? Let’s still focus on the resolveInstanceMethod method, tracking what’s being done at A, B, and C.

  1. Call the instance method sayHello of the LGPerson class and do a quick and slow search, and you can’t find the method. Finally enter the source code resolveMethod_locked -> resolveInstanceMethod process.

  2. When the code runs to A, it looks to see if the class implements resolveInstanceMethod, and if it does, it inserts the method into the cache for A quick method lookup next time. If not, return it directly. The process here is the same as the initial process!

  3. The code runs to B and sends MSG, which executes the resolveInstanceMethod method in class LGPerson. Since we point sayHello to sayHello1, here class_addMethod inserts the method into class_rw_ext_t, which is LGPerson’s method list;

    Method insertion process, see the following figure:

    At this point, the dynamic method resolution method has been executed once and the method implementation has been reconfigured.

  4. The code runs to C and looks for sayHello again. The process finds the method implementation sayHello1 in the list of methods and inserts the method cache as sel=sayHello, IMP =sayHello1 implementation.

    • C placeCall path:lookUpImpOrNilTryCache -> _lookUpImpTryCache -> lookUpImpOrForward.

    • Enter thelookUpImpOrForwardTo findsayHelloMethods.

    It is finally found in the method list and inserted into the cache.

  5. Continue to run the code, back to resolveMethod_locked and call again lookUpImpOrForwardTryCache method, carries on the method to find the.

    At this point, the method implementation is found through cache_getImp, which is implemented as sayHello1 and returns IMP.

Questions:

ResolveInstanceMethod returns NO to continue the forwarding process, and YES to stop the forwarding process.

  • In fact, if I rewrite itresolveInstanceMethodDo nothing, just returnYESIt also follows the subsequent forwarding process. This return value has no meaning for the message forwarding process fromruntimeThe source code looks at this return value and onlydebugInformation related to.

5. Class method dynamic resolution

For class methods, similar to instance methods, you can also override the resolveClassMethod class method by overriding it in the LGPerson class and referring the say666 class method implementation to the say6661 class method.

+(BOOL)resolveClassMethod:(SEL) SEL {NSLog(@" give you a chance... + + + "); if (sel == @selector(say666)) { Class meteCls = objc_getMetaClass("LGPerson"); IMP imp = class_getMethodImplementation(meteCls, @selector(say6661)); Method method = class_getInstanceMethod(meteCls, @selector(say6661)); return class_addMethod(meteCls, sel, imp, method_getTypeEncoding(method)); } return [super resolveClassMethod:sel]; }Copy the code

Instructions:

Rewriting resolveClassMethod class methods need to note that the incoming CLS is no longer a class, but a metaclass, can be obtained through objc_getMetaClass metaclass, because class methods is the instance method in the metaclass.

3. Dynamic method resolution use optimization

The above method is overridden separately in each class, is there a better, once and for all method? In fact, through the method slow search process, it can be found that there are two search paths:

  • Instance methods:Class -- parent -- root -- nil
  • Class method:Metaclass -- root metaclass -- root class -- nil

What they all have in common is that if they didn’t find it, they all go to the root class, which is NSObject, so can we integrate these two methods together? The answer is yes, by adding classes to NSObject, and because class methods look for instance methods in their inheritance chain, you can put the unified processing of instance methods and class methods in the resolveInstanceMethod method, as follows:

// Implementation NSObject @implementation NSObject (GF) + (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(sayHello)) { NSLog(@"%@ give you a chance..." , NSStringFromSelector(sel)); IMP imp = class_getMethodImplementation(self, @selector(sayHello1)); Method sayMethod = class_getInstanceMethod(self, @selector(sayHello1)); const char *type = method_getTypeEncoding(sayHello1); return class_addMethod(self, sel, imp, type); }else if (sel == @selector(say666)) {NSLog(@"%@ give you a chance +++", NSStringFromSelector(sel)); IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(say6661)); Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(say6661)); const char *type = method_getTypeEncoding(say6661); return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type); } return NO; } @endCopy the code

This way of implementation, just with the source code for the class method processing logic is consistent, that is, the perfect explanation of why the call class method dynamic method resolution, but also call the object method dynamic method resolution, the root cause of the class method in the metaclass instance method.

Above this kind of writing, of course, there will be other problems, such as system method also can be changed, for this, can be optimized, namely we can according to the custom class method unified method name prefix, judging by the prefix is a custom method, and then unified handling custom methods, for example can pop before collapse to the home page, It is mainly used for anti-crash processing of app online and improve user experience.

4. Dynamic method resolution executes two explorations

In the case of object method resolution resolveInstanceMethod, we can write an example to test this by calling an unimplemented SEL and overwriting resolveInstanceMethod without redirecting the method, only to find that the method was called twice.

See the sample screenshot below:

As you can see from the above case results, resolveInstanceMethod dynamic resolution method gives you a chance… It’s printed twice. Why is that?

By looking at the stack information through BT, we can see:

  • First dynamic resolution: The first resolution, consistent with our analysis, is not found when looking for sayHello, will enter the dynamic method resolution, send resolveInstanceMethod message.

  • The second dynamic resolution: is the second time, in the framework calls the CoreFoundation NSObject (NSObject) methodSignatureForSelector: after, will once again into the dynamic resolution.