The first two objc_msgSend quick lookup and objc_msgSend slow lookup processes mainly analyze the fast lookup cache through the assembly process and slow lookup through the method list of the class. This chapter will focus on the in-depth analysis of the previous two chapters when the method is not found, Apple provides developers with two suggestions.

  1. Dynamic method parsing: Not found during a slow searchIMP, will be executed onceDynamic method parsing
  2. forwardIf the dynamic method resolution is still not foundIMPAnd began toforward

0x00 – forward_imp

If neither of the above steps is done, a crash is reported that is not implemented in the common error method of daily development

Example code:

@interface Student : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;

- (void)sayNB;
- (void)sayMaster;
- (void)say666;
- (void)sayHello;

+ (void)sayNB;
+ (void)lgClassMethod;
@end
  
@implements Student
- (void)sayHello{
    NSLog(@"%s",__func__);
}
- (void)sayNB{
    NSLog(@"%s",__func__);
}
- (void)sayMaster{
    NSLog(@"%s",__func__);
}
+ (void)lgClassMethod{
    NSLog(@"%s",__func__);
}
@end
Copy the code
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [Student alloc];
        [stu say666];
        //[Student performSelector:@selector(sayNB)];
    }
    return 0;
}
Copy the code

Call instance method and class method in main method,

  • Calling class methods

  • Analysis:Slow to findIn the source code ofIMPIf it is not found, it will assign a value calledforward_imp=(IMP)_objc_msgForward_impcache;, by searching_objc_msgForward_impcache, found in the corresponding schema assembly
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

Search __objc_forward_handler, removing an underscore according to the rules summarized earlier.

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

The actual essence is to call objc_defaultForwardHandler, which is the crash error we see all the time. Here’s an in-depth look at remedies before a crash occurs

0x01 – Dynamic resolution of the method

In the lookUpImpOrForward method, after the slow lookup of the method is completed, the method dynamic resolution process begins, giving the developer the first chance to deal with the failure to find the message.

// 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

Through comments can also be learned that this is the IMP did not find, will go here to solve, and only go once.

/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked(a);ASSERT(cls->isRealized());

    runtimeLock.unlock(a);if (! cls->isMetaClass()) {// Check if it is a class method
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);// Call the parsing method of the instance
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls); }}// chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
Copy the code
  • Mainly divided into the following steps:

    1. Is CLS a metaclass?

      1. If it isclass, the callObject methodsDynamic analysis ofresolveInstanceMethod
      2. If it isThe metaclass, the callClass methodDynamic analysis ofresolveClassMethodTo process it and determine if it can be foundselIf not found, call againresolveInstanceMethod, because the class method, i.eMethod with a + signRelative to theThe metaclassIt’s also an instance method calledresolveInstanceMethodThe first parameter isInst = classAnd the secondThe lookup is the sel method nameAnd the thirdCLS = metaclass.
      if (! lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { // Resolver not implemented. return; }Copy the code

      If the CLS ->ISA root metaclass is used to find the implementation of the resolution method, send a message if it cannot find the default implementation.

Instance method crash fix

+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(say666)) {NSLog(@"%@ ", NSStringFromSelector(SEL)); // Get the sayMaster method imp imp imp = class_getMethodImplementation(self, @selector(sayMaster)); SayMethod = class_getInstanceMethod(self, @selector(sayMaster)); Const char *type = method_getTypeEncoding(sayMethod); // Get sayMaster method signature const char *type = method_getTypeEncoding(sayMethod); SayMaster return class_addMethod(self, sel, imp, type); } return [super resolveInstanceMethod:sel]; }Copy the code

Override the class method resolveInstanceMethod in the class. Before the message crashes, a dynamic resolution of the instance method is performed. In this method, the runtime points the missing SEL to an existing IMP and prints the result

You’ll see this method printed twice here, but I’ll leave it at the end of this article.

Class method crash fix

< span style = “max-length: 1em; clear: both; clear: both;

+ (BOOL)resolveClassMethod:(SEL) SEL {if (SEL == @selector(sayNB)) {NSLog(@"%@ ", NSStringFromSelector(SEL)); IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); const char *type = method_getTypeEncoding(lgClassMethod); return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type); } return [super resolveClassMethod:sel]; }Copy the code

โš ๏ธ To get a class method, add a class method to the metaclass. Use objc_getMetaClass to get the metaclass.

Summary and optimization

Through the dynamic analytical analysis of the above method, such a conclusion can be obtained

  • Instance methodsClass -> parent -> root -> nil
  • ResolveClassMethod metaclass -> parent metaclass -> root metaclass -> root class -> nil
  • Class method (resolveInstanceMethod) Root metaclass -> root class -> nil

ResolveInstanceMethod or resolveClassMethod is overridden in the corresponding class. You can override resolveInstanceMethod in the root class NSObject to handle crashes of both instance methods and class methods.

ResolveInstanceMethod has a default implementation in NSObject

+ (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}
Copy the code

As follows, to create a classification of NSObject, unified handling as follows, because the default implementation, it returns NO, can’t call [super resolveInstanceMethod: sel] :

+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(say666)) {NSLog(@"%@ ", NSStringFromSelector(SEL)); IMP imp = class_getMethodImplementation(self, @selector(sayMaster)); Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster)); const char *type = method_getTypeEncoding(sayMethod); return class_addMethod(self, sel, imp, type); }else if (sel == @selector(sayNB)) {NSLog(@"%@ ", NSStringFromSelector(sel)); IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); const char *type = method_getTypeEncoding(lgClassMethod); return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type); } return NO; }Copy the code

, of course, this way of unified processing, there will be some problems, some systems will enter here, you can for unified method name prefix in class, according to the prefix judgment corresponding module to handle, such as mime module, belong to the collapse of the module and unified front page jump to mime module, report can also do some wrong operation.

0x03 – Message Forwarding Process

In the fast search + slow search is not found and dynamic message parsing is not processed, will enter the message forwarding process

At the end of lookUpImpOrForward, log_and_fill_cache has a control condition, objcMsgLogEnabled, that controls the log being saved locally and the flow of the call

Control the objcMsgLogEnabled is this function instrumentObjcMessageSends, give it to true, open log local preservation control

Find the following source code implementation via lookUpImpOrForward -> log_AND_fill_cache -> logMessageSend

bool objcMsgLogEnabled = false;
static int objcMsgLogFD = - 1;

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

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

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

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if(objcMsgLogFD ! =- 1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}
Copy the code

Because in this instrumentObjcMessageSends is internal functions, external use need to use the extern external declaration

extern void instrumentObjcMessageSends(BOOL flag);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        instrumentObjcMessageSends(YES);
        [person sayHello];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
Copy the code

Through the above source code to understand the log save path in/tmp/msgSendsDirectory, run the code, and you can see the following

At the beginning of open directory msgSends file, call the resolveInstanceMethod method, was not in dynamic analytic processing method, so come to forwardingTargetForSelector fast forwarding and subsequent slowly forward

0x04 – Hopper/IDA disassembler look at the flow

Hopper and IDA is a disassembly tool that can help us with static analysis. It disassembles executable files into pseudocode and flow charts to help us with analysis. Since IDA is unstable on MAC, it can be tested on Windows.

After running a crash, look at the stack information through BT,

Viewed in assembly, __forwarding___ is also in CoreFoundation.

throughimage listDebugging Command ViewCoreFoundation imageThe location of the

Once you find CoreFoundation, open it with Hopper

Open Hopper, Try the Demo, and drag in CoreFoundation

Click OK

By default, CLICK Next

Wait for the load to complete,

Search __forwarding_prep_0___ to see the pseudocode and jump to the pseudocode inside ___forwarding___

First of all determine whether forwardingTargetForSelector

  • The jump to is not implementedloc_64a67
  • Can find implementation goloc_649fcThrough theforwardingTargetForSelectorGets the receiving object toraxAnd then toraxError tolerant processing, error jumploc_64e3c
loc_64a67Pseudo code

After the jump to here, first of all determine whether as zombies object, continue to determine whether methodSignatureForSelector response below,

  • Does not respond to jump toloc_64dd7, directly report an error
  • The response then goes down, gets the return value, makes the fault tolerant processing, has the error to jumploc_64e3c

loc_64dd7Pseudo code andloc_64e3cPseudo code

By gettingmethodSignatureForSelectorMethod signature isnilErrors are also reported directly

The above process gets the method signature and begins processing in the forwardInvocation method

Therefore, through the above analysis, there are two types of message forwarding

  • Fast forwardforwardingTargetForSelector
  • Slowly forwardmethodSignatureForSelector +forwardingTargetForSelectorimplementation

In lookUpImpOrForward, Slow doesn’t find IMP either,

  • Step 1 startDynamic resolution of methodsProcessing. If this step is not processed, goforward
  • forwardStep 1 startforwardingTargetForSelector, i.e.,Fast message forwarding, forwards the message to another object for processing. This step is not processedSlowly forward
  • Slowly forwardusemethodSignatureForSelectorReturn method signature, cannot returnnilorThe signature is empty, using method signature generationNSInvocationObject, so it needs to be overriddenforwardInvocationForwarding the message.

0x05 – resolveInstanceMethodWhy two executions?

To solve the problems left over before, in the instance dynamic method parsing, only rewrite, not to find sel processing, will call two times

Exploration of God’s perspective

LookUpImpOrForward -> resolveMethod_locked -> resolveInstanceMethod when an instance dynamic method is resolved

IMP IMP = lookUpImpOrNil(INST, SEL, CLS); Add a breakpoint, when sel is say666 stop, print say66 is coming and look at the stack through bt,

The first printed message, as can be seen from the stack, was printed the first time the method was dynamically resolved.

By printing it a second time, Through [NSObject (NSObject) methodSignatureForSelector:] – > __methodDescriptionForSelector – > Class_getInstanceMethod again came to the method of dynamic analysis and print the second, by analyzing the stack, can through the Hopper disassembling CoreFoundation file, check methodSignatureForSelector pseudo code

In jumped to ___methodDescriptionForSelector see its implementation

In conjunction with the previous stack letter view, objC’s class_getInstanceMethod method is called and viewed in the source code project

/*********************************************************************** * class_getInstanceMethod. Return the instance method for the * specified class and selector. **********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if(! cls || ! sel)return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}
Copy the code

Through the source view, again call lookUpImpOrForward here, and go a dynamic analytic method, system after call methodSignatureForSelector, return to the method signature, before the call invocation, Class_getInstanceMethod is called again, so lookUpImpOrForward is used again and SEL is queried. The dynamic parsing and message forwarding process are not found again.

No God perspective exploration

Because in the source code project exploration, so there is a God perspective, if there is no environment, how to verify the above process?

In a normal project, rewrite resolveInstanceMethod to resolve sel errors. Add an IMP using class_addMethod and see if the method passes twice.

+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(say666)) {NSLog(@"%@ ", NSStringFromSelector(SEL)); // Get sayMaster method imp imp imp = class_getMethodImplementation(self, @selector(sayHello)); SayMethod = class_getInstanceMethod(self, @selector(sayHello)); Const char *type = method_getTypeEncoding(sayMethod); // Get sayMaster method signature const char *type = method_getTypeEncoding(sayMethod); SayMaster return class_addMethod(self, sel, imp, type); } return [super resolveInstanceMethod:sel]; }Copy the code

By the result, by dynamic method resolution, IMP was assigned, only executed once, indicating that the second time is not here. According to the forward process, remove the imp in resolveInstanceMethod, rewrite forwardingTargetForSelector, and specify the [LGStudent alloc], to run, See if resolveInstanceMethod print two times, print two times, that before forwardingTargetForSelector carried out dynamic analytic method, on the other hand, is in after the execution of dynamic analytic method.

+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(say666)) {NSLog(@"%s -- %@ ",__func__, NSStringFromSelector(sel)); // // get sayMaster method imp // imp imp = class_getMethodImplementation(self, @selector(sayHello)); SayMethod = class_getInstanceMethod(self, @selector(sayHello)); // // Get sayMaster's method signature // const char *type = method_getTypeEncoding(sayMethod); // // redirect sel implementation to sayMaster // return class_addMethod(self, sel, imp, type); } return [super resolveInstanceMethod:sel]; } - (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector)); // runtime + aSelector + addMethod + imp return [LGStudent alloc]; }Copy the code

By running the results did not see, before promised to two times, shows that after forwardingTargetForSelector perform dynamic analytic method

According to the process, then rewrite methodSignatureForSelector and forwardInvocation

+ (BOOL)resolveInstanceMethod: SEL SEL {NSLog(@"%s -- %@ ",__func__, NSStringFromSelector(SEL)); If (sel == @selector(say666)) {// // get the imp of sayMaster method // imp imp = class_getMethodImplementation(self, @selector(sayHello)); SayMethod = class_getInstanceMethod(self, @selector(sayHello)); // // Get sayMaster's method signature // const char *type = method_getTypeEncoding(sayMethod); // // redirect sel implementation to sayMaster // return class_addMethod(self, sel, imp, type); } return [super resolveInstanceMethod:sel]; } - (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector)); return [super forwardingTargetForSelector:aSelector]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector)); NSLog(@"%p", [NSMethodSignature signatureWithObjCTypes:"v@:@"]); return [NSMethodSignature signatureWithObjCTypes:"v@"]; } - (void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@"%s - %@",__func__,anInvocation); Target = [LGStudent alloc]; // Invocation invocation - Method [anInvocation invocation]; }Copy the code

Dynamic resolution through the above analysis, the second is between methodSignatureForSelector and forwardInvocation invocation, the second kind of analytical method validation results and the first kind of the disassembly result is the same. The result is the following graph

conclusion

This article is the last one of message flow analysis, method dynamic analysis and message forwarding

  • First of all,The messagethroughThe assembly processQuick lookup, no found jumplookupImpOrForwardStart slow search
  • Slow search for messagesNo luck either. GoMethod dynamic resolution
  • Method dynamic resolutionAccording to the news,Class methodorInstance methodsrewriteresolveInstanceMethodandresolveClassMethodMethod, start the first remedy
  • Method dynamic resolutionIt does not process the message and starts to forward the message, namely [fast forwarding].
  • Fast forward, that is, to rewrite theforwardingTargetForSelectorMethod to dump the message to a processableobject, for a second remedy
  • Slowly forwardusemethodSignatureForSelectorReturn method signature, cannot returnnilorThe signature is empty, using method signature generationNSInvocationObject, so it needs to be overriddenforwardInvocationForwarding the message.

Objective-c method signature and invocation

Principle and Practice of iOS Development ยท Runtime: Message Forwarding (Message mechanism, method not implemented +API incompatible crash, simulation of multiple inheritance)


Welcome big brother message correction ๐Ÿ˜„, code word is not easy, feel good to give a thumbs-up ๐Ÿ‘ have any expression or understanding error please leave a message; Common progress;