美文网首页
10、方法的调用原理(3)

10、方法的调用原理(3)

作者: 白马啸红中 | 来源:发表于2021-01-28 10:28 被阅读0次

    由于这章分析比较重要和前一章联系紧密,因此用的旧的系统,和objc781.1的源码,还有个原因就是当时用自己电脑更新到Big Sur时候,编译源码报错发现找不到Foundation的库,根据路径找一下发现真的缺少相应的mach-o文件,不方便这一章分析

    2.方法调用的流程
    2.4——消息决议与消息转发

    书接上文,继10、方法的调用原理(2),知道在快速查找慢速查找后如果还没用找到相应的方法,也就是在cache中没有相应的方法缓存,类的方法列表中也没有找到对应的imp,那会怎么处理这个情况,下面就来研究一下。

    2.4.1——方法调用失败情形

    结合上一篇的源码:

    @interface Person : NSObject
    @property (nonatomic,assign)   int age;
    @property (nonatomic,strong)   NSString *nickname;
    @property (nonatomic,assign)   float height;
    @property (nonatomic,strong)   NSString *name;
    
    -(void)laugh;
    -(void)cry;
    -(void)run;
    -(void)jump;
    -(void)doNothing;
    @end
    
    @implementation Person
    -(void)laugh
    {
        NSLog(@"LMAO");
    }
    
    -(void)cry
    {
        NSLog(@"cry me a river");
    }
    
    -(void)run
    {
        NSLog(@"run! Forrest run!");
    }
    
    -(void)jump
    {
        NSLog(@"you jump,I jump!");
    }
    
    -(void)doNothing
    {
        NSLog(@"Today,I dont wanna do anything~");
    }
    
    
    //-(void)talkShow
    //{
    //    NSLog(@"roast king!");
    //}
    
    
    @end
    
    @interface Saler : Person
    
    @property (nonatomic,strong)   NSString *brand;
    
    
    -(void)talkShow;
    
    +(void)sayGiao;
    
    @end
    
    @implementation Saler
    
    //-(void)talkShow
    //{
    //    NSLog(@"roast king!");
    //}
    
    
    //+(void)sayGiao
    //{
    //    NSLog(@"one geli my giao!");
    //}
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //main方法中
            Saler *person =  [Saler alloc];
            
            person.age = 10;
            person.nickname = @"pp";
            person.height = 180.0;
            person.name = @"ppext";
            person.brand = @"apple";
    
            Saler *saler = [Saler alloc];
            saler.age = 28;
            saler.nickname = @"xx";
            saler.height = 175.0;
            saler.name = @"xxext";
            saler.brand = @"apple";
    //实例方法
    //        [saler talkShow];
    //类方法
            [Saler sayGiao];   
        }
        return 0;
    }
    

    肯定会报错:

    报错:方法无法识别 字面意识就是发送给类的selector无法识别,同样的实例方法的调用失败情形也相似。
    2.4.2——消息决议情形研究

    慢速查找的截止方法resolveMethod_locked直接翻译过来就是方法决议,分析其源码:

    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 (!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);
    }
    

    可以知道分两种,一个是决议实例方法resolveInstanceMethod,一个是决议类方法resolveClassMethod,最后还是会走入到lookUpImpOrForward这个方法,进行方法决议方法的调用。这里为了方便直观,将类方法注销,改用talkShow方法来演示,方便断点调试,在跟踪resolveMethod_locked直到lookUpImpOrForward最后返回imp,可以大致上知道resolveMethod_locked的流程:
    1、根据是否是类方法,选择resolveInstanceMethod还是resolveClassMethod;(这里以resolveInstanceMethod为例子)
    2、通过lookUpImpOrForward查找resolveInstanceMethod否存在imp,此处不会进入resolveMethod_locked方法,因为NSObject必定实现了resolveInstanceMethod方法
    3、找到resolveInstanceMethod的实现,发送resolveInstanceMethod消息,调用msg(cls, resolve_sel, sel),msg就是objc_msgSend的方法指针,未找到resolveInstanceMethod的实现,啥也不做直接return,因为NSObject必定实现了,所以这里基本上都是走发送消息
    4、调用msg(cls, resolve_sel, sel)后,会将resolveInstanceMethod对应的imp加入到方法cache中,也就是走一次lookUpImpOrForward但是不会进入resolveMethod_locked
    5、resolveInstanceMethod调用完成,回到resolveMethod_locked的lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE)方法,此处会进入resolveMethod_locked方法,因为此时behavior==LOOKUP_INITIALIZE再|LOOKUP_CACHE,在之前的代码if (fastpath(behavior & LOOKUP_CACHE))就注定是大概率进入分支了,走done_nolock,结束了resolveMethod_locked方法
    6、运行done_nolock代码块,返回_objc_msgForward_impcache
    最终调试打印的imp信息就是当时初始化的(IMP)_objc_msgForward_impcache,所以验证了8、方法的调用原理(2)的判断不管慢速查找的终点是forward_imp还是resolveMethod_locked结果都是返回(IMP)_objc_msgForward_impcache

    既然imp对应的是_objc_msgForward_impcache方法,那这个方法源码要研究一下,全局搜索:

        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
    

    发现最终调用的是__objc_forward_handler方法,继续全局搜索,发现搜不到。去掉一个下划线搜索_objc_forward_handler

    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
    

    发现等价于objc_defaultForwardHandler方法,在同一文件中可以找到:

    __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)) ? '+' : '-', 
                    object_getClassName(self), sel_getName(sel), self);
    }
    

    这个就相当于最早看到的报错信息了,结合方法决议的流程可以大致得到方法调用失败情形:
    1、慢速搜索失败,进入方法决议
    2、根据是否是类方法判断是走resolveInstanceMethod或者resolveClassMethod
    3、通过消息发送流程发送方法决议消息,调用方法决议方法,提供可修补机会
    4、最后在调用lookUpImpOrForward方法,搜索决议后的类信息,看是否还能够找到方法,如果找不到就返回_objc_msgForward_impcache进行报错,找到了就返回相应的imp

    2.4.2——方法决议后续流程研究

    接着上一节分析,这里需要特别注意的一点就是如果你在Saler类中实现了resolveInstanceMethod方法:

    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        NSLog(@"--------%s",(char *)sel);
        
        return YES;
    }
    

    并打上断点,可以发现这个方法是被调用两次了的,这样就意味着msg(cls, resolve_sel, sel)是发送了两次,为什么会这样需要研究一下:

    方法调用失败流程已经研究的差不多,但是方法决议流程是通过代码预测的,下面对流程进行调试:
    这里先在Saler类中实现一下resolveInstanceMethod,并在里面打下断点

    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        NSLog(@"--------%s",(char *)sel);
        
        return YES;
    }
    

    运行调试一下,可以发现在崩溃前决议方法进入两次,为啥进入两次?因此添加bt命令查看堆栈:

    第一次进入决议方法 第二次进入决议方法 可以知道:
    第一次进入,根据堆栈信息就知道是发送方法决议消息调用的,不多做分析了。
    第二次进入,堆栈是由:
    1、__forwarding_prep_0___
    2、___forwarding___
    3、-[NSObject(NSObject) methodSignatureForSelector:]
    4、__methodDescriptionForSelector
    5、class_getInstanceMethod(cls=Saler, sel="talkShow")
    6、lookUpImpOrForward(inst=0x0000000000000000, sel="talkShow", cls=Saler, behavior=0)
    7、resolveMethod_locked(inst=0x0000000000000000, sel="talkShow", cls=Saler, behavior=0)
    8、resolveInstanceMethod(inst=0x0000000000000000, sel="talkShow", cls=Saler)
    9、+[Saler resolveInstanceMethod:]
    在这个流程中知道是调用了methodSignatureForSelector方法之后会调用__methodDescriptionForSelector而这个方法会调用class_getInstanceMethod方法,才导致的再次进入lookUpImpOrForward方法,再次触发方法决议的,那就看看class_getInstanceMethod源码是什么样子的:
    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);
    }
    

    和堆栈的调用一样的,就是直接调用的lookUpImpOrForward方法,查找methodlist以及父类cache,根据调用关系,肯定是查找methodSignature的,如果这里在子类中实现了methodSignatureForSelector,就不会走NSObject的了,而且在子类不添加处理的情况下绝对是找不到方法的,所以在后续方法决议也无法找到。

    其中5-9方法流程与第一次进入的方法类似就不着重研究了,1-4方法都是无法搜索到源码的,但是都是属于CoreFoundation库中的方法,因此只有查看一下这个库中是怎么实现的。
    首先确定CoreFoundation库的引用路径,在lldb调试中输入image list查看应用加载的镜像列表:

    镜像列表中的CoreFoundation 找到CoreFoundation文件 下面要运用IDA或者hopper来对这个mach-o文件进行进行反编译来探究一下方法执行。这里选择的是hopper,使用demo版本,直接拖入mach-o文件,一路ok,直接搜索__forwarding_prep_0___,顶部segment选择伪代码展示:
    int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
        var_20 = rax;
        var_30 = zero_extend_64(xmm7);
        var_40 = zero_extend_64(xmm6);
        var_50 = zero_extend_64(xmm5);
        var_60 = zero_extend_64(xmm4);
        var_70 = zero_extend_64(xmm3);
        var_80 = zero_extend_64(xmm2);
        var_90 = zero_extend_64(xmm1);
        var_A0 = zero_extend_64(xmm0);
        var_A8 = arg5;
        var_B0 = arg4;
        var_B8 = arg3;
        var_C0 = arg2;
        var_C8 = arg1;
        rax = ____forwarding___(&var_D0, 0x0);
        if (rax != 0x0) {
                rax = *rax;
        }
        else {
                rax = objc_msgSend(var_D0, var_C8);
        }
        return rax;
    }
    

    就会发现调用的是____forwarding___,再搜索一下,找到伪代码:

    int ____forwarding___(int arg0, int arg1) {
    //0
        rsi = arg1;
        rdi = arg0;
        r15 = rdi;
        rcx = COND_BYTE_SET(NE);
        if (rsi != 0x0) {
                r12 = *_objc_msgSend_stret;
        }
        else {
                r12 = *_objc_msgSend;
        }
        rax = rcx;
        rbx = *(r15 + rax * 0x8);
        rcx = *(r15 + rax * 0x8 + 0x8);
        var_140 = rcx;
        r13 = rax * 0x8;
        if ((rbx & 0x1) == 0x0) goto loc_649bb;
    //1
    loc_6498b:
        rcx = **_objc_debug_taggedpointer_obfuscator;
        rcx = rcx ^ rbx;
        rax = rcx >> 0x1 & 0x7;
        if (rax == 0x7) {
                rcx = rcx >> 0x4;
                rax = (rcx & 0xff) + 0x8;
        }
        if (rax == 0x0) goto loc_64d48;
    //2
    loc_649bb:
        var_148 = r13;
        var_138 = r12;
        var_158 = rsi;
        rax = object_getClass(rbx);
        r12 = rax;
        r13 = class_getName(rax);
        if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;
    //3
    loc_649fc:
        rdi = rbx;
        rax = [rdi forwardingTargetForSelector:var_140];
        if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;
    //4
    loc_64a19:
        r12 = var_138;
        r13 = var_148;
        if ((rax & 0x1) == 0x0) goto loc_64a5b;
    //5
    loc_64a2b:
        rdx = **_objc_debug_taggedpointer_obfuscator;
        rdx = rdx ^ rax;
        rcx = rdx >> 0x1 & 0x7;
        if (rcx == 0x7) {
                rcx = (rdx >> 0x4 & 0xff) + 0x8;
        }
        if (rcx == 0x0) goto loc_64d45;
    //6
    loc_64a5b:
        *(r15 + r13) = rax;
        r15 = 0x0;
        goto loc_64d82;
    //7
    loc_64d82:
        if (**___stack_chk_guard == **___stack_chk_guard) {
                rax = r15;
        }
        else {
                rax = __stack_chk_fail();
        }
        return rax;
    //8
    loc_64d45:
        rbx = rax;
        goto loc_64d48;
    //9
    loc_64d48:
        if ((*(int8_t *)__$e48aedf37b9edb179d065231b52a648b & 0x10) != 0x0) goto loc_64ed1;
    //10
    loc_64d55:
        *(r15 + r13) = _getAtomTarget(rbx);
        ___invoking___(r12, r15);
        if (*r15 == rax) {
                *r15 = rbx;
        }
        goto loc_64d82;
    //11
    loc_64ed1:
        ____forwarding___.cold.4();
        rax = *(rdi + 0x8);
        return rax;
    //12
    loc_64a67:
        var_138 = rbx;
        if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;
    //13
    loc_64a8a:
        rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
        r14 = var_138;
        var_148 = r15;
        if (rax == 0x0) goto loc_64dd7;
    //14
    loc_64ab2:
        rax = [r14 methodSignatureForSelector:var_140];
        rbx = var_158;
        if (rax == 0x0) goto loc_64e3c;
    //15
    loc_64ad5:
        r12 = rax;
        rax = [rax _frameDescriptor];
        r13 = rax;
        if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
                rax = sel_getName(var_140);
                rcx = "";
                if ((*(int16_t *)(*r13 + 0x22) & 0xffff & 0x40) == 0x0) {
                        rcx = " not";
                }
                r8 = "";
                if (rbx == 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, stack[-360]);
        }
        rax = object_getClass(r14);
        rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
        var_150 = r13;
        if (rax == 0x0) goto loc_64c19;
    //16
    loc_64b6c:
        if (*0x5c2700 != 0xffffffffffffffff) {
                dispatch_once(0x5c2700, ^ {/* block implemented at ______forwarding____block_invoke */ } });
        }
        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;
    //17
    loc_64c76:
        if (*(int8_t *)(r13 + 0x34) != 0x0) {
                rax = *var_150;
                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 = [r12 methodReturnType];
        rbx = rax;
        rax = *(int8_t *)rax;
        if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) {
                r15 = *(r13 + 0x10);
                if (r14 != 0x0) {
                        r15 = [[NSData dataWithBytes:r15 length:var_140] bytes];
                        [r13 release];
                        rax = *(int8_t *)rbx;
                }
                if (rax == 0x44) {
                        asm { fld        tword [r15] };
                }
        }
        else {
                r15 = ____forwarding___.placeholder;
                if (r14 != 0x0) {
                        r15 = ____forwarding___.placeholder;
                        [r13 release];
                }
        }
        goto loc_64d82;
    //18
    loc_64c19:
        if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;
    //19
    loc_64c3b:
        rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
        r13 = rax;
        [r14 forwardInvocation:rax];
        var_140 = 0x0;
        r14 = 0x0;
        goto loc_64c76;
    //20
    loc_64ec2:
        rdi = &var_130;
        ____forwarding___.cold.3(rdi, r14);
        goto loc_64ed1;
    //21
    loc_64e3c:
        rax = sel_getName(var_140);
        r14 = rax;
        rax = sel_getUid(rax);
        if (rax != var_140) {
                _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, rax, r9, stack[-360]);
        }
        if (class_respondsToSelector(object_getClass(var_138), @selector(doesNotRecognizeSelector:)) == 0x0) {
                ____forwarding___.cold.2(var_138);
        }
        (*_objc_msgSend)(var_138, @selector(doesNotRecognizeSelector:));
        asm { ud2 };
        rax = loc_64ec2(rdi, rsi);
        return rax;
    //22
    loc_64dd7:
        rbx = class_getSuperclass(r12);
        r14 = object_getClassName(r14);
        if (rbx == 0x0) {
                _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[-360]);
        }
        else {
                _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[-360]);
        }
        goto loc_64e3c;
    //23
    loc_64dc1:
        r14 = @selector(forwardingTargetForSelector:);
        ____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
        goto loc_64dd7;
    }
    

    看到伪代码,可以从头开始分析(先捋清分支和关键字段)
    代码段0
    不满足条件是走loc_6498b(代码段1),满足条件是走loc_649bb(代码段2)

    loc_6498b(代码段1)
    不满足条件是走loc_649bb(代码段2),满足条件是走loc_64d48(代码段9)

    loc_649bb(代码段2)
    判断类是否响应forwardingTargetForSelector:方法,如果响应则走loc_64a67(代码段12),不响应则走loc_649fc(代码段3)

    loc_649fc(代码段3)
    直接用原始实例或者类来调用forwardingTargetForSelector:方法,如果失败或者forwardingTargetForSelector:转发的类与方法调用类是同一个就走loc_64a67 (代码段12),不满足就走loc_64a19(代码段4)

    loc_64d48(代码段9)
    判断满足条件,走loc_64ed1(代码段11),不满足走loc_64d55(代码段10)

    loc_64ed1(代码段11)
    返回,结束流程。

    loc_64d55(代码段10)
    loc_64d82(代码段7)

    loc_64d82(代码段7)
    返回,结束流程。

    分析到这里,基本上可以知道了,并不是只调用了methodDescriptionForSelectormethodSignatureForSelector方法,而是先调用了forwardingTargetForSelector转发方法。
    所以,大致流程是:
    先调用决议方法resolveInstanceMethod、再调用快速转发方法forwardingTargetForSelector、最后调用慢速转发方法methodSignatureForSelectormethodDescriptionForSelector

    再结合后续伪代码分析,可以知道其实还有两个方法forwardInvocationdoesNotRecognizeSelector,结合之前的一些消息转发机制的一些信息可以知道这几个方法都是在流程之内。

    验证一下,在代码中添加这几个测试方法断点验证一下调用先后顺序:

    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        NSLog(@"--------%@",@"resolveInstanceMethod");
        
        return [super resolveInstanceMethod:sel];
    }
    
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        NSLog(@"--------%@",@"methodSignatureForSelector");
        
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    //    return [super methodSignatureForSelector:aSelector];
    }
    
    -(void)forwardInvocation:(NSInvocation *)anInvocation
    {
        NSLog(@"--------%@",@"forwardInvocation");
        return [super forwardInvocation:anInvocation];
    }
    
    -(void)doesNotRecognizeSelector:(SEL)aSelector
    {
        NSLog(@"--------%@",@"doesNotRecognizeSelector");
    }
    
    返回方法签名的调用 注释掉返回方法签名后,打开super方法调用:
    未返回方法签名的打印 验证了之前推断的流程,方法决议的流程基本清楚,利用和methodSignatureForSelectorforwardInvocation的慢速转发流程也大致了解,还有就是forwardingTargetForSelector的快速转发流程。
    2.4.3——消息转发流程研究

    结合上一章分析,知道:
    方法决议resolveInstanceMethod OR resolveClassMethod
    快速转发forwardingTargetForSelector
    慢速转发methodSignatureForSelectorforwardInvocation
    识别失败doesNotRecognizeSelector

    首先实现resolveInstanceMethod方法,添加imp,其余的方法不用注销:

    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        NSLog(@"--------%@",@"resolveInstanceMethod");
        if ([NSStringFromSelector(sel) isEqualToString:@"talkShow"]) {
    
            void(^block)(void) = ^{
                NSLog(@"talkShow_added");
            };
    
            class_addMethod([Saler class], sel, imp_implementationWithBlock(block), "v@:");
        }
        return [super resolveInstanceMethod:sel];
    }
    
    决议方法实现 这样的尝试就知道实现了决议方法,后续的流程就不会走了。

    再看快速转发的研究,注销掉决议方法,还是用决议方法的那种实现,只不过返回的实例还是Saler,不然不会在Saler类中查找imp:

    -(id)forwardingTargetForSelector:(SEL)aSelector
    {
        NSLog(@"--------%@",@"forwardingTargetForSelector");
        if ([NSStringFromSelector(aSelector) isEqualToString:@"talkShow"]) {
            
            void(^block)(void) = ^{
                NSLog(@"talkShow_added");
            };
            
            class_addMethod([Saler class], aSelector, imp_implementationWithBlock(block), "v@:");
        }
        
        return [Saler new];
    //    return [super forwardingTargetForSelector:aSelector];
    }
    
    快速转发的实现 这样的尝试就可以知道如果返回的实例或者如果还是没实现imp一样会报错,快速转发相当于转发给有这个方法imp的类,可以尝试另外写一个实现了talkShow方法的来作为返回也会得到和这个相同效果。

    再看慢速转发的研究,注销掉决议方法快速转发方法,还是用决议方法的那种实现,只不过返回的实例还是Saler,不然不会在Saler类中查找imp:

    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        NSLog(@"--------%@",@"methodSignatureForSelector");
    
        //这里必须返回这个不能用super,super返回的是nil会略过forwardInvocation方法
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    //    return [super methodSignatureForSelector:aSelector];
    }
    //
    -(void)forwardInvocation:(NSInvocation *)anInvocation
    {
        NSLog(@"--------%@",@"forwardInvocation");
    
        void(^block)(void) = ^{
                   NSLog(@"talkShow_added");
               };
    
        class_addMethod([Saler class], NSSelectorFromString(@"talkShow"), imp_implementationWithBlock(block), "v@:");
        [anInvocation invokeWithTarget:[Saler new]];
        
    //    [super forwardInvocation:anInvocation];
    }
    
    慢速转发的实现 对比之前的方法决议快速转发,他们的宽容程度是递进的:
    1、决议方法是修补类信息达到再次寻找imp可以找到的目的;
    2、快速转发方法不止修补类信息,可以直接将消息转发给其他类来实现;
    3、慢速转发方法更上一层楼,不止前两项都兼容,甚至连方法都可以改变。

    消息决议和消息转发流程总结:
    1、实现决议方法 resolveInstanceMethod后仍然找不到方法的imp的话,就会走快速转发 forwardingTargetForSelector
    2、实现快速转发方法 forwardingTargetForSelector仍然找不到方法的imp的话,就会走慢速转发
    3、实现慢速转发方法 methodSignatureForSelector 和 forwardInvocation如果还是找不到imp的话,就会走doesNotRecognizeSelector(这里并不是抛出错误),这个方法执行完之后才会报错;
    4、上述步骤中的方法如果有一样没有实现,且仍然找不到imp才会抛出unrecognized selector sent to instance错误。
    5、上述步骤中的方法如果任一实现了,且在后续方法调用中找到了imp后续步骤将不会走了。


    方法调用流程总结:

    1、方法调用;
    2、转换为消息发送Objc_msgSend;
    3、快速查找类信息中的Cache中查找方法的实现imp
    4、找不到,慢速查找类信息中的methodlist查找方法的实现imp
    5、找不到,进入lookUpImpOrForward然后进入方法动态决议提供修补类信息机会
    6、动态决议后仍然找不到,进入快速转发可以将消息转发至其他可以实现的对象
    7、如果其他对象还是没有找到,就进入慢速转发,看修改方法信息和实现对象后还可不可以找到这个方法的实现imp
    8、仍然无法找到,就放弃了,直接调用doesNotRecognizeSelector后报错unrecognized selector sent to instance

    相关文章

      网友评论

          本文标题:10、方法的调用原理(3)

          本文链接:https://www.haomeiwen.com/subject/dhyloktx.html