Following on from the previous article, message forwarding occurs if the method invocation does not implement dynamic method resolution. Consumption forwarding is divided into two stages as follows:

Fast forward

To achieve fast forwarding, we need to implement the following functions without implementing the dynamic method resolution:

// Transfer method calls to other objects.- (id)forwardingTargetForSelector:(SEL)aSelector
+(id)forwardingTargetForSelector:(SEL)aSelector
Copy the code

Code demo:

#import <Foundation/Foundation.h>

@interface ABPerson : NSObject
@end
@implementation ABPerson
- (void)doSomething
{
    NSLog(@"%s",__func__);
}
@end
@interface ABPeople : NSObject
- (void)doSomething;
@end
@implementation ABPeople- (id)forwardingTargetForSelector:(SEL)aSelector{
     
    NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
    return  [ABPerson alloc];
}

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        ABPeople *p = [ABPeople new];
       
        [p doSomething];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
Copy the code

ABPeopleThe unimplemented method is handed overABPersonImplement and call. But what if fast forwarding doesn’t work either? Let’s do a slow forwarding.

Slowly forward

// Return the method signature with the method number
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
Copy the code
// The Target caller and the sel method are saved in the anInvocation- (void)forwardInvocation:(NSInvocation *)anInvocation
+(void)forwardInvocation:(NSInvocation *)anInvocation
Copy the code

Both methods must be implemented at the same time; the forwardInvocation can be empty without an error.

Code demo:

#import <Foundation/Foundation.h>

@interface ABPeople : NSObject
- (void)doSomething;
@end
@implementation ABPeople

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(doSomething)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [supermethodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@ % @ - % @ "",anInvocation.target,NSStringFromSelector(anInvocation.selector));
    ABPerson *p = [ABPerson new];
    if([p respondsToSelector:anInvocation.selector]) { anInvocation.target = p; [anInvocation invoke]; }}@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        ABPeople *p = [ABPeople new];
       
        [p doSomething];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
Copy the code

Hopper disassembles CoreFoundation

When the method cannot be found and crashes, we print the call stack with the bt command:

Can’t find the way to the last processingdoesNotRecognizeSelectorExecute the things in the red box and you can see that they come fromCoreFoundation.

Drag the CoreFoundation executable onto the Hopper Disassemalber to view the disassembly pseudocode.

Global search forwarding

___forwarding_prep_0___Call the ____forwarding___To see the ____forwarding___Implementation:

int ____forwarding___(int arg0, int arg1){omit some pseudocodeloc_649bb:
    var_148 = r13;
    var_138 = r12;
    var_158 = rsi;
    rax = object_getClass(rbx);
    r12 = rax;
    r13 = class_getName(rax);
    / / when the class does not implement forwardingTargetForSelector, execute loc_64a67
    if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;

loc_649fc:
    rdi = rbx;
    / / when a class implements the forwardingTargetForSelector, it returns rax (object)
    rax = [rdi forwardingTargetForSelector:var_140];
    // If rax does not exist or is equal to the original object, loc_64a67 is executed
    if ((rax == 0x0) || (rax == rbx)) goto loc_64a67; Omit some pseudocodeloc_64a67:
    var_138 = rbx;
    // Check if it is a zombie object
    if (strncmp(r13, "_NSZombie_".0xa) = =0x0) goto loc_64dc1;

loc_64a8a:
   / / when the class does not implement methodSignatureForSelector, execute loc_64dd7
    rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
    r14 = var_138;
    var_148 = r15;
    if (rax == 0x0) goto loc_64dd7;

loc_64ab2:
 / / when a class implements the methodSignatureForSelector, it returns rax (object)
    rax = [r14 methodSignatureForSelector:var_140];
    rbx = var_158;
    // Execute loc_64e3c without implementation
    if (rax == 0x0) goto loc_64e3c; Omit some pseudocode// The system implementation _forwardStackInvocation is not exposed
    rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
    var_150 = r13;
    if (rax == 0x0) goto loc_64c19;

loc_64b6c:
    if (*0x5c2700! =0xffffffffffffffff) {
            dispatch_once(0x5c2700The ^ {/* block implemented at ______forwarding____block_invoke */}}); }//NSInvocation some handling
    r15 = [NSInvocation requiredStackSizeForSignature:r12];
    rsi = *0x5c26f8;
    rsp = rsp - ___chkstk_darwin(@class(NSInvocation), rsi.r12.rcx);
    r13 = &stack[-360];
    __bzero(r13, rsi);
    ___chkstk_darwin(r13, rsi, r12, rcx);
    rax = objc_constructInstance(*0x5c26f0, r13);
    var_140 = r15;
    [r13 _initWithMethodSignature:r12 frame:var_148 buffer:&stack[-360] size:r15];
    [var_138 _forwardStackInvocation:r13];
    r14 = 0x1; goto loc_64c76; Omit some pseudocodeloc_64c19:
// invocation loc_64ec2 if the class can't respond to the forwardInvocation
    if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;

loc_64c3b:
    rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
    r13 = rax;
    // start the call
    [r14 forwardInvocation:rax];
    var_140 = 0x0;
    r14 = 0x0;
    goto loc_64c76;

loc_64ec2:
    rdi = &var_130;
    ____forwarding___.cold3.(rdi, r14);
    goto loc_64ed1;

Copy the code

Message Forwarding Flowchart

conclusion

The message forwarding process (find IMP through SEL) is as follows:

  1. lookUpImpOrForwardA slow search failedimp
  2. resolveMethod_lockedDetermine the currentclassIs it class
  3. Is a class calledresolveClassMethodEnforce class dynamic method resolution
  4. Is a class calledresolveInstanceMethodExecute object dynamic method resolution
  5. If the dynamic method resolution is not implemented or the processing fails, check whether fast forwarding is implementedforwardingTargetForSelector
  6. If fast forwarding is not implemented, check whether slow forwarding is implemented:methodSignatureForSelectorReturn signature,forwardInvocationCalls to perform
  7. If slow forwarding is also required, thedoesNotRecognizeSelector
  8. Dynamic method resolution, fast forward, slow forward any stage of success, directly processing the message.

supplement

When the method is implemented, the dynamic method resolution is printed twice

Objc project debugging:

Make a breakpoint inside this file function

The second time is about to output the currentselPrint the function call stack

The reason for this is that CoreFoundation calls __forwarding_prep_0___ and the system calls class_respondsToSelector_inst. The call process is as follows:

__forwarding_prep_0___ – > class_respondsToSelector_inst – > lookUpImpOrNilTryCache – > lookUpImpOrForward.

The idea is that if an IMP is added during message forwarding, it can be processed again.