preface

The search flow of the method described above: fast search and slow slow search flow. But it does not cover how the system handles when the method implementation is not found, which is covered in this article.

1. Methodological resolution

If (slowpath(behavior & LOOKUP_RESOLVER)) {//LOOKUP_RESOLVER = 2 behavior = 3 // slowpath(behavior & LOOKUP_RESOLVER) = 1 LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); }Copy the code

When the slow lookup does not find, the method resolution phase — resolveMethod_locked — is entered.

Before entering resolveMethod_locked to view method resolutions, take a look at the implementation and functions of several important methods.

1.1 resolveInstanceMethod: and resolveClassMethod:

Nsobject. h + (BOOL)resolveClassMethod:(SEL) SEL OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + (BOOL)resolveInstanceMethod: SEL OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); NSObject.mm + (BOOL)resolveClassMethod:(SEL)sel { return NO; } + (BOOL)resolveInstanceMethod:(SEL)sel { return NO; }Copy the code

ResolveClassMethod: Provides a dynamic implementation of a given selector for a class method. ResolveInstanceMethod: Provides a dynamic implementation for the given selector of the instance method. These two class methods are declared and implemented in the base class NSObject to dynamically add method implementations to the current SEL if an IMP is not found during a slow lookup. By default, NO is returned. Subclasses can override this method, add a dynamic implementation for the specified SEL, and return YES, indicating that the method’s dynamic resolution took place.

Take the official example: An Objective-C method is just a C function that takes at least two arguments — self and _cmd. You can use this function to add functions as methods to a class.

void dynamicMethodIMP(id self, SEL _cmd)
{
    // implementation ....
}
Copy the code

Given the following function: class_addMethod, it can be used to dynamically add dynamicMethodIMP as method (called) to the class, as shown in the following: resolveInstanceMethod: resolveThisMethodDynamically

+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically))
    {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSel];
}
Copy the code

1.2 resolveInstanceMethod/resolveClassMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); // register resolveInstanceMethod: the method signature is prepared for calling the method. // SEL resolve_sel = @selector(resolveInstanceMethod:); ResolveInstanceMethod if (! lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) { // Resolver not implemented. return; BOOL (* MSG)(Class, SEL, SEL) = (typeof(MSG))objc_msgSend; bool resolved = msg(cls, resolve_sel, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. CLS / / find the sel imp imp may is empty during the execution of a / / lookUpImpOrNilTryCache invokes lookUpImpOrForward, / / if after resolution method, a method of sel imp, after a search, Imp imp = lookUpImpOrNilTryCache(INST, SEL, CLS); 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)); }}}Copy the code

The resolveInstanceMethod method is called for method resolution, followed by an IMP lookup. LookUpImpOrNilTryCache may return nil. The result, IMP, is not returned, but is used for output records. But imPs found (whether the actual method implementation or forward_IMP) are cached and can be found directly in the cache during subsequent look-ups. Sometimes resolveInstanceMethod: may return YES, but does not actually add the method implementation. So after a search, we can finally confirm. Then record the output of the resolution.

For the resolveClassMethod, see resolveInstanceMethod.

1.3 lookUpImpOrNilTryCache/lookUpImpOrForwardTryCache

LookUpImpOrNilTryCache returns nil if imp is not found. Behaviors | LOOKUP_NIL makes behaviors & LOOKUP_NIL result is true. See the end of lookUpImpOrForward for details.

IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    //behavior | LOOKUP_NIL = 7
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
Copy the code

LookUpImpOrForwardTryCache, find imp, will get back to return to a default _objc_msgForward_impcache method implementation.

IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior);
}
Copy the code

1.4 _lookUpImpTryCache

static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior) { // behavior = 1 runtimeLock.assertUnlocked(); if (slowpath(! CLS ->isInitialized()) {// look back at lookUpImpOrForward. Return lookUpImpOrForward(inst, sel, CLS, behavior); IMP IMP = cache_getImp(CLS, sel); //imp is not empty and returns if (imp! = NULL) goto done; #if CONFIG_USE_PREOPT_CACHES if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) { imp = cache_getImp(cls->cache.preoptFallbackClass(), sel); If (slowpath(imp == NULL)) {return lookUpImpOrForward(inst, sel, CLS, behavior); } // return imp or nil done: if ((behavior & LOOKUP_NIL) && IMP == (imp)_objc_msgForward_impcache) {return nil; } return imp; }Copy the code

Lookupimptrycache is a standard IMP lookup method. Look for cache_getImp first. If not, look for lookUpImpOrForward slowly.

Note that if _lookUpImpTryCache is executed during a slow search for a method, the first slow search is not found, the method resolution is performed, and the method resolution is followed by the search again. If lookUpImpOrForward is executed, which is the second time it has been executed, behavior has xOR with LOOKUP_RESOLVER (value 2) and and with LOOKUP_RESOLVER (2) is no longer true. So it’s impossible to make a method decision again.

1.5 resolveMethod_locked

The resolveMethod_locked method resolution process is much clearer after the previous methods.

static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { //behavior=1 runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); if (! CLS - > isMetaClass ()) {/ / CLS isn't metaclass inst for instance objects sel instance method resolution for instance methods / / / / try/CLS resolveInstanceMethod: sel resolveInstanceMethod(inst, sel, cls); } else {/ / CLS is a metaclass inst is sel class object is a class method / / try [nonMetaClass resolveClassMethod: sel] / / and/CLS resolveInstanceMethod: sel ResolveClassMethod (inst, sel, CLS); // Call lookUpImpOrNilTryCache to determine if sel has imp if (! LookUpImpOrNilTryCache (inst, sel, CLS) {// If the imp is still not found, try again. ResolveInstanceMethod (inst, sel, CLS); resolveInstanceMethod(inst, sel, CLS); // chances are that calling the resolver have populated the cache // so attempt using it // Select * from sel, select * from sel, select * from sel, select * from sel, select * from sel, select * from sel But _objc_msgForward_impcache) / / return the result return lookUpImpOrForwardTryCache (inst, sel, CLS, behaviors); }Copy the code

ResolveMethod_locked evaluates the current CLS and performs a different method resolution process. If CLS is not a metaclass, then the current INST is the instance object sel is the instance method, and the instance method resolution process is performed. If CLS is a metaclass, then currently inst is a class object, sel is a class method, and the class method resolution process is executed:

  • Let’s start with class method resolutionresolveClassMethod
  • And then findimp, determine whether SEL has a corresponding IMP after class method resolution
  • If the IMP is still not found, do the instance method resolution again

Why can we do instance method resolution again? Isa bitmaps do, and the parent of the root metaclass is the NSObject class. The instance methods that are stored in NSObject are added to the instance resolution methods in NSObject, so you can do instance method resolution again.

In the end, will be called lookUpImpOrForwardTryCache, imp lookup and returns the result (may be real imp, also may be _objc_msgForward_impcache).

We already know how to do it up front

resolveInstanceMethod(inst, sel, cls);
Copy the code

or

resolveClassMethod(inst, sel, cls);
Copy the code

An IMP lookup has already been done inside the method. The execution lookUpImpOrForwardTryCache will find the imp in the cache.

For example, 1.6

With all that said, let’s test it with an example.

Define the ZPerson class and declare the sayHello and sayNB methods, but only the sayNB method is implemented. At the same time, rewrite the method resolveInstanceMethod and add print to confirm that the IMP is not found and will enter the method resolution.

@interface ZPerson : NSObject - (void)sayHello; - (void)sayNB; @end@implementation ZPerson //- (void)sayHello{// NSLog(@" hello "); } - (void)sayNB{NSLog(@" you are so cool "); } + (BOOL)resolveInstanceMethod: SEL SEL {NSLog(@" enter instance method dynamic resolution "); return [super resolveInstanceMethod:sel]; } @endCopy the code

Create a ZPerson instance and execute the sayHello method. Since there is no implementation of sayHello or +resolveInstanceMethod, it will crash.

ZPerson *person = [ZPerson alloc];
[person sayHello];
Copy the code

Execution Result:

As expected, the program crashed without finding a real way to implement it (2). At the same time, +resolveInstanceMethod :(1) is executed before the error message is output.

We noticed that +resolveInstanceMethod: was executed twice. Why is this explained later?

Here we add a dynamic method implementation for sayHello in the +resolveInstanceMethod: method:

+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(sayHello)) {NSLog(@" execute sayHello method resolution "); IMP sayHelloIMP = class_getMethodImplementation(self, @selector(sayNB)); Method sayHelloMethod = class_getInstanceMethod(self, @selector(sayNB)); const char *sayHelloType = method_getTypeEncoding(sayHelloMethod); return class_addMethod(self, sel, sayHelloIMP, sayHelloType); } return [super resolveInstanceMethod:sel]; }Copy the code

In the resolution approach, dynamic provides an implementation for sayHello, binding imp of sayNB to SEL of sayHello.

Enter instance method dynamic resolution: sayHello executes sayHello method resolution you are awesomeCopy the code

It won’t crash. The sayNB method is executed instead.

A thought about crashes: Since we can add dynamic implementations for methods in resolveInstanceMethod:, can we avoid crashes caused by such problems in the future? That’s ok. We can override this method in the class of NSObject to unify the processing. And either instance or class methods can be dynamically implemented in resolveInstanceMethod:. But there are some drawbacks. When system methods may change, you can solve this problem by adding prefixes to custom methods. This is very intrusive, and if a subclass overrides this method, it won’t execute NSObject.

2. Forwarding messages

2.1 objcMsgLogEnabled

To continue thinking about the previous question, when the SEL is not handled correctly in resolveInstanceMethod:, this method will be executed twice, but we can not find the second time to execute the method resolution after we look through the whole process of the method resolution. Our guess is that it might have done something before the system processed the collapse.

To view the crash stack:What we found is that frommainTo the dynamic library to throw an exceptionobjc_exception_throw + 48I passed through the middleCoreFoundationLibrary processing. butCoreFoundationIt’s not open source, so we don’t know what’s going on yet.

But we in front of the method slow search process, know that you can output search IMP log to the file, we try, can find some clues.

instrumentObjcMessageSends(true);
[person sayHello];
Copy the code

Before calling sayHello, open objcMsgLogEnabled. Then view the corresponding log file.

+ ZPerson NSObject resolveInstanceMethod:
+ ZPerson NSObject resolveInstanceMethod:
- ZPerson NSObject forwardingTargetForSelector:
- ZPerson NSObject forwardingTargetForSelector:
- ZPerson NSObject methodSignatureForSelector:
- ZPerson NSObject methodSignatureForSelector:
+ ZPerson NSObject resolveInstanceMethod:
+ ZPerson NSObject resolveInstanceMethod:
- ZPerson NSObject doesNotRecognizeSelector:
- ZPerson NSObject doesNotRecognizeSelector:

Copy the code

Find the following methods:

  • resolveInstanceMethod:
  • forwardingTargetForSelector:
  • methodSignatureForSelector:
  • doesNotRecognizeSelector:

The reason why there are two methods is that these methods check for the existence of the method before execution, and record the slow search once. Since there is no caching, when it actually executes, it does another slow lookup and another record, each time the above method has two records.

We see after the methodSignatureForSelector: doesNotRecognizeSelector: before method, and execute the method resolution resolveInstanceMethod: at a time. Is it safe to assume that Apple gave another chance to find the method before the error was reported? Please continue to watch

Let’s look at the methods in sequence, according to the order in which they are recorded.

2.2 forwardingTargetForSelector

inobjcSource searchforwardingTargetForSelector Found only inNSObjectDefinitions and default implementations are provided in. No more information was found. But it’s okay, since it isOCMethod, let’s go to the official documents first.

From the documentation, you probably know that this method returns an object that is used to receive and process the incoming methodsel. You can also returnnil, but cannot return itself (self) It gets into a loop.

Default implementation in NSObeject:

- (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}
Copy the code

You can use this method if you only want to redirect a message to another object, and it can be an order of magnitude faster than normal forwarding, so it is called fast forwarding. It cannot be used if the purpose of message forwarding is to capture THE NSInvocation or operate parameters or return values during the invocation. Use forwardInvocation:, slow forwarding.

Let’s try this: Define the ZStudent class and implement the sayHello method.

@interface ZStudent : NSObject - (void)sayHello; @end - (void)sayHello{NSLog(@" student say: hello "); }Copy the code

Rewrite forwardingTargetForSelector in ZPerson class: method, will sayHello message, to an ZStudent object. Note: Comment out the method resolution above.

- (id) forwardingTargetForSelector (SEL) aSelector {NSLog (@ "fast forwarding forwardingTargetForSelector: %@", NSStringFromSelector(aSelector)); if (aSelector == @selector(sayHello)) { return [ZStudent alloc]; } return [super forwardingTargetForSelector:aSelector]; }Copy the code

Execution Result:

Enter the instance methods dynamic resolution: sayHello fast forwarding forwardingTargetForSelector: sayHello student said: helloCopy the code

The program does not crash and executes a method resolution before entering the method’s fast forward.

This is fast forwarding of messages, and you can hand a message that the current object cannot process to an object that can process the message.

ForwardingTargetForSelector method to realize if there is no fast forwarding or not not incoming SEL returns a replace message receiver, the system will enter the slow forward process.

2.3 forwardInvocation

When an object receives a message that has no corresponding method implementation, the runtime system gives the receiver an opportunity to delegate the message to another receiver. It does this by creating oneNSInvocationObject to wrap messages that need to be delegated,NSInvocationContains a message receivertarget, a method selectorsel, the parameters and return values of the message.NSInvocationObject passed to the receiver as a parameterforwardInvocationMethod to forward the message to another object. (If the object is also unable to respond to the message, it will also have the opportunity to forward it.)

The default implementation of NSObject:

+ (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
Copy the code

The default call doesNotRecognizeSelector:.

DoesNotRecognizeSelector: default implementation in the Core Foundation.

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}
Copy the code

If the subclass implements forwardInvocation:

- (void)forwardInvocation:(NSInvocation *)invocation
{
    SEL aSelector = [invocation selector];
 
    if ([friend respondsToSelector:aSelector])
        [invocation invokeWithTarget:friend];
    else
        [super forwardInvocation:invocation];
}
Copy the code

Note 1:

In addition to realize forwardInvocation: methods, also need to rewrite methodSignatureForSelector: method. System to get the information you need to create NSInvocation object, need methodSignatureForSelector: method for the given selector SEL provide a method signature. MethodSignatureForSelector: it returns nil, so as not to perform forwardInvocation:.

Note 2: NSObjectThe default implementation provided simply calls methods; It does not forward any messages. Therefore, if you choose not to implementForwardInvocation:, sending an unrecognized message to the object will throw an exception that will executedoesNotRecognizeSelector:Methods.

2.4 methodSignatureForSelector

MethodSignatureForSelector: method, returns a NSMethodSignature object, the object contains a given selector SEL the description of the method. It can also return nil if the method isn’t found.

MethodSignatureForSelector: default implementation in the Core Foundation

// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("-[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}
Copy the code

Let’s look at the implementation process of slow message forwarding.

- (id) forwardingTargetForSelector (SEL) aSelector {NSLog (@ "fast forwarding forwardingTargetForSelector: %@", NSStringFromSelector(aSelector)); // if (aSelector == @selector(sayHello)) { // return [ZStudent alloc]; // } return [super forwardingTargetForSelector:aSelector]; } - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {NSLog (@ "slow forward methodSignatureForSelector: %@", NSStringFromSelector(aSelector)); if (aSelector == @selector(sayHello)) { NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"]; return methodSignature; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation{NSLog(@" Slow forwardInvocation: %@", anInvocation); if (anInvocation.selector == @selector(sayHello)) { [anInvocation invokeWithTarget:[ZStudent alloc]]; } else { [super forwardInvocation:anInvocation]; }}Copy the code

Start by commenting out the previous quick forward process. Finally, in the forwardInvocation: method, the ZPSerson sayHello method is forwarded to ZStudent for execution.

Results:

Enter the instance methods dynamic resolution: sayHello fast forwarding forwardingTargetForSelector: sayHello slowly forward methodSignatureForSelector: sayHello enter the instance methods dynamic resolution: _forwardStackInvocation: <NSInvocation: 0x10138B5A0 > Student: HelloCopy the code

The result, as expected, is a call to the ZPSerson object’s sayHello method, but because it is not implemented, it is forwarded to the ZZStudent object for execution.

Problem two: We see, before the message forwarding, the method signature methodSignatureForSelector: for message forwarding forwardInvocation: before, after and carried out a method of resolution, But this time the resolution method becomes _forwardStackInvocation:. Why is that?

With questions 1 and 2, proceed to the following assembly analysis.

3. Assembly analysis of forwarding process

  1. Find CoreFoundation. Framework: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes /iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework

  2. Open Hoper and drag in the CoreFoundation executable.

  3. Since the analysis is on the MAC, the x86 64 architecture is chosen.

4. Search____forwarding___

5. Window ->Show Pseudo Code of Procedure

int ____forwarding___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
    r9 = arg5;
    r8 = arg4;
    rcx = arg3;
    r13 = arg1;
    r15 = arg0;
    rax = COND_BYTE_SET(NE);
    if (arg1 != 0x0) {
            r12 = *_objc_msgSend_stret;
    }
    else {
            r12 = *_objc_msgSend;
    }
    rbx = *(r15 + rax * 0x8);
    rsi = *(r15 + rax * 0x8 + 0x8);
    var_140 = rax * 0x8;
    if (rbx >= 0x0) goto loc_115af7;

loc_115ac0:
    rax = *_objc_debug_taggedpointer_obfuscator;
    rax = *rax;
    rcx = (rax ^ rbx) >> 0x3c & 0x7;
    rax = ((rax ^ rbx) >> 0x34 & 0xff) + 0x8;
    if (rcx != 0x7) {
            rax = rcx;
    }
    if (rax == 0x0) goto loc_115ea6;

loc_115af7:
    var_150 = r12;
    var_138 = rsi;
    var_148 = r15;
    rax = object_getClass(rbx);
    r15 = rax;
    r12 = class_getName(rax);
    if (class_respondsToSelector(r15, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_115bab;

loc_115b38:
    rax = [rbx forwardingTargetForSelector:var_138];
    if ((rax == 0x0) || (rax == rbx)) goto loc_115bab;

loc_115b55:
    if (rax >= 0x0) goto loc_115b91;

loc_115b5a:
    rcx = *_objc_debug_taggedpointer_obfuscator;
    rcx = *rcx;
    rdx = (rcx ^ rax) >> 0x3c & 0x7;
    rcx = ((rcx ^ rax) >> 0x34 & 0xff) + 0x8;
    if (rdx != 0x7) {
            rcx = rdx;
    }
    if (rcx == 0x0) goto loc_115e95;

loc_115b91:
    *(var_148 + var_140) = rax;
    r15 = 0x0;
    goto loc_115ef1;

loc_115ef1:
    if (**___stack_chk_guard == **___stack_chk_guard) {
            rax = r15;
    }
    else {
            rax = __stack_chk_fail();
    }
    return rax;

loc_115e95:
    rbx = rax;
    r15 = var_148;
    r12 = var_150;
    goto loc_115ea6;

loc_115ea6:
    if (dyld_program_sdk_at_least(0x7e30901ffffffff) != 0x0) goto loc_116040;

loc_115ebd:
    r14 = _getAtomTarget(rbx);
    *(r15 + var_140) = r14;
    ___invoking___(r12, r15, r15, 0x400, 0x0, r9, var_150, var_148, var_140, var_138, var_130, stack[-304], stack[-296], stack[-288], stack[-280], stack[-272], stack[-264], stack[-256], stack[-248], stack[-240]);
    if (*r15 == r14) {
            *r15 = rbx;
    }
    goto loc_115ef1;

loc_116040:
    ____forwarding___.cold.1();
    rax = objc_opt_class(@class(NSInvocation));
    *____forwarding___.invClass = rax;
    rax = class_getInstanceSize(rax);
    *____forwarding___.invClassSize = rax;
    return rax;

loc_115bab:
    var_140 = rbx;
    if (strncmp(r12, "_NSZombie_", 0xa) == 0x0) goto loc_115f30;

loc_115bce:
    r14 = var_140;
    if (class_respondsToSelector(r15, @selector(methodSignatureForSelector:)) == 0x0) goto loc_115f46;

loc_115bef:
    rbx = var_138;
    rax = [r14 methodSignatureForSelector:rbx];
    if (rax == 0x0) goto loc_115fc1;

loc_115c0e:
    r15 = rax;
    rax = [rax _frameDescriptor];
    r12 = rax;
    if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != r13) {
            rax = sel_getName(rbx);
            rcx = "";
            if ((*(int16_t *)(*r12 + 0x22) & 0xffff & 0x40) == 0x0) {
                    rcx = " not";
            }
            r8 = "";
            if (r13 == 0x0) {
                    r8 = " not";
            }
            _CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.", rax, rcx, r8, r9, var_150);
    }
    if (class_respondsToSelector(object_getClass(r14), @selector(_forwardStackInvocation:)) == 0x0) goto loc_115d61;

loc_115c9a:
    if (*____forwarding___.onceToken != 0xffffffffffffffff) {
            dispatch_once(____forwarding___.onceToken, ^ {/* block implemented at ______forwarding____block_invoke */ } });
    }
    [NSInvocation requiredStackSizeForSignature:r15];
    var_138 = r15;
    rdx = *____forwarding___.invClassSize;
    r13 = &var_150 - (rdx + 0xf & 0xfffffffffffffff0);
    memset(r13, 0x0, rdx);
    objc_constructInstance(*____forwarding___.invClass, r13);
    var_150 = rax;
    r15 = var_138;
    [r13 _initWithMethodSignature:var_138 frame:var_148 buffer:&stack[-8] - (0xf + rax & 0xfffffffffffffff0) size:rax];
    [var_140 _forwardStackInvocation:r13];
    rbx = 0x1;
    goto loc_115dce;

loc_115dce:
    if (*(int8_t *)(r13 + 0x34) != 0x0) {
            rax = *r12;
            if (*(int8_t *)(rax + 0x22) < 0x0) {
                    rcx = *(int32_t *)(rax + 0x1c);
                    rdx = *(int8_t *)(rax + 0x20) & 0xff;
                    memmove(*(rdx + var_148 + rcx), *(rdx + rcx + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
            }
    }
    rax = [r15 methodReturnType];
    r14 = rax;
    rax = *(int8_t *)rax;
    if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(r14 + 0x1) != 0x76)))) {
            r15 = *(r13 + 0x10);
            if (rbx != 0x0) {
                    r15 = [[NSData dataWithBytes:r15 length:var_150] bytes];
                    [r13 release];
                    rax = *(int8_t *)r14;
            }
            if (rax == 0x44) {
                    asm { fld        tword [r15] };
            }
    }
    else {
            r15 = ____forwarding___.placeholder;
            if (rbx != 0x0) {
                    r15 = ____forwarding___.placeholder;
                    [r13 release];
            }
    }
    goto loc_115ef1;

loc_115d61:
    var_138 = r12;
    r12 = r14;
    if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_115f8e;

loc_115d8d:
    rax = [NSInvocation _invocationWithMethodSignature:r15 frame:var_148];
    r13 = rax;
    [r12 forwardInvocation:rax];
    var_150 = 0x0;
    rbx = 0x0;
    r12 = var_138;
    goto loc_115dce;

loc_115f8e:
    r14 = @selector(forwardInvocation:);
    ____forwarding___.cold.4(&var_130, r12);
    rcx = r14;
    _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_140, rcx, r8, r9, var_150);
    goto loc_115fba;

loc_115fba:
    rbx = var_138;
    goto loc_115fc1;

loc_115fc1:
    rax = sel_getName(rbx);
    r14 = rax;
    rax = sel_getUid(rax);
    if (rax != rbx) {
            rcx = r14;
            r8 = rax;
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_138, rcx, r8, r9, var_150);
    }
    if (class_respondsToSelector(object_getClass(var_140), @selector(doesNotRecognizeSelector:)) == 0x0) goto loc_116034;

loc_11601b:
    [var_140 doesNotRecognizeSelector:rdx];
    asm { ud2 };
    rax = loc_116034(rdi, rsi, rdx, rcx, r8, r9);
    return rax;

loc_116034:
    ____forwarding___.cold.3(var_140);
    goto loc_116040;

loc_115f46:
    rbx = class_getSuperclass(r15);
    r14 = object_getClassName(r14);
    if (rbx == 0x0) {
            rax = object_getClassName(var_140);
            rcx = r14;
            r8 = rax;
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_140, rcx, r8, r9, var_150);
    }
    else {
            rcx = r14;
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_140, rcx, r8, r9, var_150);
    }
    goto loc_115fba;

loc_115f30:
    r14 = @selector(forwardingTargetForSelector:);
    ____forwarding___.cold.2(var_140, r12, var_138, rcx, r8);
    goto loc_115f46;
}

Copy the code

3.1 forwardingTargetForSelector:

Enter the____forwarding___, will be fast forward first.

Procedure 1: Callclass_respondsToSelectorMethods. To find theforwardingTargetForSelectorWhether there is. If so, proceed to procedure 2. If not, jumploc_115bab. Procedure 2: Perform fast forwardingforwardingTargetForSelector. If returned asnilOr return the object asself, also jumps toloc_115babTo carry out. Otherwise, the execution continues untilloc_115ef1“, the execution ends.

Class_respondsToSelector:

/***********************************************************************
* class_respondsToSelector.
**********************************************************************/
BOOL class_respondsToMethod(Class cls, SEL sel)
{
    OBJC_WARN_DEPRECATED;

    return class_respondsToSelector(cls, sel);
}


BOOL class_respondsToSelector(Class cls, SEL sel)
{
    return class_respondsToSelector_inst(nil, sel, cls);
}


// inst is an instance of cls or a subclass thereof, or nil if none is known.
// Non-nil inst is faster in some cases. See lookUpImpOrForward() for details.
NEVER_INLINE __attribute__((flatten)) BOOL
class_respondsToSelector_inst(id inst, SEL sel, Class cls)
{
    // Avoids +initialize because it historically did so.
    // We're not returning a callable IMP anyway.
    return sel && cls && lookUpImpOrNilTryCache(inst, sel, cls, LOOKUP_RESOLVER);
}
Copy the code

Class_respondsToSelector, implemented in objc-class.mm, is used to find out if the specified sel exists in the CLS of the executing class, and _lookUpImpTryCache is eventually called. _lookUpImpTryCache: a fast search is performed first, and a slow search is performed if the search fails.

3.2 methodSignatureForSelector:

loc_141957: performloc_141957After zombie object judgment, the execution continues. Similarly, check firstmethodSignatureForSelector:If yes, continuemethodSignatureForSelector:.

When there is no methodSignatureForSelector:, or methodSignatureForSelector: return result is empty, will eventually perform to loc_115fc1: doesNotRecognizeSelector:. When methodSignatureForSelector: existence and methodSignatureForSelector: return the result isn’t empty, will continue to execute down, loc_115c0e:.

Core FoundationformethodSignatureForSelectorProvides a default implementation, double clickmethodSignatureForSelector:, pop-ups appearSelect item 1 to enter the instance methodmethodSignatureForSelectorThe default implementation of.

/* @class NSObject */ -(void *)methodSignatureForSelector:(void *)arg2 { rdx = arg2; rdi = self; if ((rdx ! = 0x0) && (___methodDescriptionForSelector(object_getClass(rdi), rdx) ! = 0x0)) { rax = [NSMethodSignature signatureWithObjCTypes:rdx]; } else { rax = 0x0; } return rax; }Copy the code

If there will be an if judgment, arg2 is not null, and ___methodDescriptionForSelector execution result is empty, it returns a NSMethodSignature object, otherwise it returns null.

objc_opt_class

Class objc_opt_class(id obj) { #if __OBJC2__ if (slowpath(! obj)) return nil; Class cls = obj->getIsa(); if (fastpath(! cls->hasCustomCore())) { return cls->isMetaClass() ? obj : cls; } #endif return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class)); }Copy the code

Returns nil when obj is null. Otherwise return obj’s Class.

___methodDescriptionForSelector

int ___methodDescriptionForSelector(int arg0, int arg1) { rbx = arg1; var_40 = arg0; if (arg0 == 0x0) goto loc_120833; loc_120755: r12 = var_40; var_50 = rbx; goto loc_12075d; loc_12075d: var_34 = 0x0; rax = class_copyProtocolList(r12, &var_34); if (var_34 == 0x0) goto loc_1207ea; loc_120776: r13 = 0x0; var_48 = rax; goto loc_12077d; loc_12077d: r15 = *(rax + r13 * 0x8); rdi = r12; r14 = r12; r12 = rax; rax = class_isMetaClass(rdi); rax = protocol_getMethodDescription(r15, rbx, 0x1, (rax ^ 0x1) & 0xff); if (rax ! = 0x0) goto loc_1207f6; loc_1207a9: r15 = *(r12 + r13 * 0x8); rax = class_isMetaClass(r14); rax = protocol_getMethodDescription(r15, rbx, 0x0, (rax ^ 0x1) & 0xff); r12 = r14; if (rax ! = 0x0) goto loc_120804; loc_1207cf: r13 = r13 + 0x1; rbx = var_50; rax = var_48; if (r13 < var_34) goto loc_12077d; loc_1207e2: rbx = 0x0; r15 = 0x0; goto loc_12080e; loc_12080e: free(rax); if (r15 ! = 0x0) goto loc_12085a; loc_12081b: rax = class_getSuperclass(r12); r12 = rax; rbx = var_50; if (rax ! = 0x0) goto loc_12075d; loc_120833: rax = class_getInstanceMethod(var_40, rbx); if (rax ! = 0x0) { rax = method_getDescription(rax); r15 = *rax; rbx = *(rax + 0x8); } else { rbx = 0x0; r15 = 0x0; } goto loc_12085a; loc_12085a: if (**___stack_chk_guard == **___stack_chk_guard) { rax = r15; } else { rax = __stack_chk_fail(); } return rax; loc_120804: r15 = rax; rbx = 0x0; rax = var_48; goto loc_12080e; loc_1207f6: r15 = rax; rbx = 0x1; rax = r12; r12 = r14; goto loc_12080e; loc_1207ea: if (rax == 0x0) goto loc_12081b; loc_1207ef: r15 = 0x0; rbx = 0x0; goto loc_12080e; }Copy the code

___methodDescriptionForSelector:Involves loop lookup,gotoMore, you can refer to the following flow chart to understand

  1. withclass_copyProtocolList,protocol_getMethodDescriptionMethod to check if there is a correspondingselectorWhether implemented or not
  2. withclass_getInstanceMethodcheckselectorIs there an implementation

Question one, when there is no processing sayHello method in the resolution method, no fast forwarding method, the system calls will come methodSignatureForSelector: the default implementation, to perform ___methodDescriptionForSelector method, Class_getInstanceMethod is called, which returns null.

We mentioned earlier that class_getInstanceMethod actually ends up calling the lookUpImpOrForward lookup method, so it comes back to resolveInstanceMethod: in the second slow lookup. So when the method implementation is not found, the method resolution method is executed twice if no processing is done by default.

3.3 _forwardStackInvocation:

loc_115c0e: To find the_forwardStackInvocation:This is a private method. If it cannot be found, call back toloc_115d61performforwardInvocation:.

So the question 2, can be in in methodSignatureForSelector: forwardInvocation: before, after once _forwardStackInvocation: the method of resolution.

3.4 forwardInvocation:

loc_115d61:

By analyzing the pseudo code, as long as the execution forwardInvocation:, with or without forwarding, will not be transferred to doesNotRecognizeSelector:. This is not the case, we commented out the forward code above and still crashed because we called the parent NSObject’s forwardInvocation:. So, if can’t collapse, don’t call [super forwardInvocation: anInvocation]; .

- (void)forwardInvocation:(NSInvocation *)anInvocation{NSLog(@" Slow forwardInvocation: %@", anInvocation); // if (anInvocation.selector == @selector(sayHello)) { // [anInvocation invokeWithTarget:[ZStudent alloc]]; // } else { [super forwardInvocation:anInvocation]; / /}}Copy the code

3.5 doesNotRecognizeSelector:

loc_115fc1:

Double-click to check doesNotRecognizeSelector: default implementation:

/* @class NSObject */
-(void)doesNotRecognizeSelector:(void *)arg2 {
    _CFLog(0x3, @"%@: unrecognized selector sent to instance %p", ___CFExceptionProem(self, arg2), self, r8, r9, stack[-40]);
    objc_exception_throw([NSException exceptionWithName:*_NSInvalidArgumentException reason:__CFAutoreleasePoolAddObject(*_kCFAllocatorSystemDefault, _CFStringCreateWithFormat(*_kCFAllocatorSystemDefault, 0x0, @"%@: unrecognized selector sent to instance %p"), @"%@: unrecognized selector sent to instance %p", rax, self) userInfo:0x0]);
    return;
}
Copy the code

Output an error message and throw an exception.

When the news of the unrealized and ultimately were not resolution or forwarding processing, will come to doesNotRecognizeSelector:. You can customize the implementation to avoid the final crash or upload records of the crash stack.

4. To summarize

When you try to call a method that has no implementation, the following flow occurs:

  1. resolveInstanceMethod: method resolution that dynamically adds an object to the object that sends the messageIMP, and then execute
  2. forwardingTargetForSelector: Fast forwarding: forwards the message directly to an object that can process the message
  3. methodSignatureForSelectorandforwardInvocationThe first method generates the method signature and then createsNSInvocationObject is given as an argument to the second method, and message processing is done in the second method. As long as the parent method is not executed in the second method, it will not crash if it is not processed
  4. The above three steps, handled correctly in any of the steps, will not cause a crash, otherwise an error crash will be reported