iOS底层探索消息转发

上篇内容,若是方法的调用,没有实现动态方法决议,那么就会进行消息转发。消费转发分为两个阶段分别以下:sass

快速转发

要实现快速转发须要在未实现实现动态方法决议的状况下,实现下面函数:markdown

//将方法的调用转给其余对象(本身作不了,就交给其余人作 )
-(id)forwardingTargetForSelector:(SEL)aSelector
+(id)forwardingTargetForSelector:(SEL)aSelector
复制代码

代码演示:函数

#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;
}
复制代码

image.png ABPeople没有实现的方法交给了ABPerson实现并调用。可是若是快速转发也没有实现呢?那就进行慢速转发。post

慢速转发

//经过方法编号,返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
复制代码
//调用者Target,方法sel都被保存在anInvocation中
-(void)forwardInvocation:(NSInvocation *)anInvocation
+(void)forwardInvocation:(NSInvocation *)anInvocation
复制代码

这两个方法必须同时实现,forwardInvocation能够实现空方法,也不会报错。ui

代码演示:spa

#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 [super methodSignatureForSelector: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;
}
复制代码

image.png

Hopper反汇编CoreFoundation

在方法找不到,崩溃的时候,咱们经过bt命令打印调用栈:调试

image.png 在找不到方法的最后处理doesNotRecognizeSelector,执行了红框中的东西,能看出它们来自CoreFoundationcode

CoreFoundation可执行文件拖到Hopper Disassemalber,查看反汇编伪代码。orm

全局搜索forwarding对象

image.png ___forwarding_prep_0___调用了 ____forwarding___,查看 ____forwarding___实现:

int ____forwarding___(int arg0, int arg1) {

   省略部分伪代码

loc_649bb:
    var_148 = r13;
    var_138 = r12;
    var_158 = rsi;
    rax = object_getClass(rbx);
    r12 = rax;
    r13 = class_getName(rax);
    //当类没有实现forwardingTargetForSelector,就执行loc_64a67
    if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;

loc_649fc:
    rdi = rbx;
    //当类实现了forwardingTargetForSelector,就返回rax(对象)
    rax = [rdi forwardingTargetForSelector:var_140];
    //若是rax不存在或等于原来的对象就就执行loc_64a67
    if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;

省略部分伪代码

loc_64a67:
    var_138 = rbx;
    //判断是不是僵尸对象
    if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;

loc_64a8a:
   //当类没有实现methodSignatureForSelector,就执行loc_64dd7
    rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
    r14 = var_138;
    var_148 = r15;
    if (rax == 0x0) goto loc_64dd7;

loc_64ab2:
 //当类实现了methodSignatureForSelector,就返回rax(对象)
    rax = [r14 methodSignatureForSelector:var_140];
    rbx = var_158;
    //没有实现就执行loc_64e3c
    if (rax == 0x0) goto loc_64e3c;
    
省略部分伪代码

    //系统实现的方法_forwardStackInvocation,并无对外暴露
    rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
    var_150 = r13;
    if (rax == 0x0) goto loc_64c19;

loc_64b6c:
    if (*0x5c2700 != 0xffffffffffffffff) {
            dispatch_once(0x5c2700, ^ {/* block implemented at ______forwarding____block_invoke */ } });
    }
    //NSInvocation的一些处理
    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;

省略部分伪代码

loc_64c19:
//类是否能响应forwardInvocation,不能响应就执行loc_64ec2
    if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;

loc_64c3b:
    rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
    r13 = rax;
    //开始调用
    [r14 forwardInvocation:rax];
    var_140 = 0x0;
    r14 = 0x0;
    goto loc_64c76;

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

复制代码

消息转发流程图

消息.png

总结

消息转发流程(经过sel寻找imp)以下:

  1. lookUpImpOrForward慢速查找没有找到imp
  2. resolveMethod_locked判断当前class是否是类
  3. 是类调用resolveClassMethod执行类动态方法决议
  4. 是类调用resolveInstanceMethod执行对象动态方法决议
  5. 未实现动态方法决议或未处理成功,查看是否实现快速转发forwardingTargetForSelector
  6. 未实现快速转发,就查看是否实现慢速转发:methodSignatureForSelector返回签名,forwardInvocation调用执行
  7. 若是也为实现慢速转发,就调用doesNotRecognizeSelector
  8. 动态方法决议、快速转发、慢速转发任何一个阶段成功了,就直接处理消息。

补充

当方法为实现时,动态方法决议会打印两遍

image.png

objc工程调试:

image.png 在此文件函数内打上断点

image.png 在第二次即将输出当前sel的时候,打印函数调用栈

缘由是CoreFoundation调用了__forwarding_prep_0___,通过系统处理调用了class_respondsToSelector_inst。调用流程以下:

__forwarding_prep_0___->class_respondsToSelector_inst->lookUpImpOrNilTryCache->lookUpImpOrForward

这样作的目的是在消息转发过程当中,若是imp被加上了,能够再次获得处理。

相关文章
相关标签/搜索