美文网首页
iOS runtime 七: 方法查找与动态决议

iOS runtime 七: 方法查找与动态决议

作者: Trigger_o | 来源:发表于2022-10-10 18:12 被阅读0次

    快速查找

    runtime将方法调用转换为objc_msgSend函数,尽管每个方法的返回值,参数可能不一样,
    但是objc_msgSend可以做类型转换.
    这个函数没有C++实现,直接是汇编代码实现.位于.s文件,不同的架构有不同的文件以及不同的汇编代码.
    除了objc_msgSend还有几个相关的方法objc_msgSendSuper等.
    以ENTRY为入口.

            ENTRY _objc_msgSend
        UNWIND _objc_msgSend, NoFrame
    
        NilTest NORMAL
        GetIsaFast NORMAL       // r10 = self->isa
        // calls IMP on success
        CacheLookup NORMAL, CALL, _objc_msgSend
        NilTestReturnZero NORMAL
        GetIsaSupport NORMAL
    
    // cache miss: go search the method lists
            LCacheMiss_objc_msgSend:
        // isa still in r10
        jmp __objc_msgSend_uncached
    //...
    

    这是x86_64的objc_msgSend,
    NilTest,GetIsaFast,CacheLookup等等这些都是定义在当前文件中的其他代码段.
    NilTest用于判断receive是否为空.
    GetIsaFast获取isa
    接下来CacheLookup就是快速查找IMP.
    如果没找到,进入__objc_msgSend_uncached

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    
        cmp p0, #0          // nil check and tagged pointer check
    #if SUPPORT_TAGGED_POINTERS
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    #else
        b.eq    LReturnZero
    #endif
        ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13, 1, x0  // p16 = class
    LGetIsaDone:
        // calls imp or objc_msgSend_uncached
        CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
    
    #if SUPPORT_TAGGED_POINTERS
    LNilOrTagged:
        b.eq    LReturnZero     // nil check
        GetTaggedClass
        b   LGetIsaDone
    // SUPPORT_TAGGED_POINTERS
    #endif
    
    LReturnZero:
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        ret
        END_ENTRY _objc_msgSend
    

    上面这段是arm64的_objc_msgSend.
    x86_64没有SUPPORT_TAGGED_POINTERS的情况,arm64有.
    首先也是查看receiver是否为空,如果是空,分成两种情况,一是tagged pointer,走LNilOrTagged,二是不支持tagged pointer,走LReturnZero.
    LNilOrTagged和LReturnZero就在上面代码的最后部分.

    ldr p13, [x0]
    GetClassFromIsa_p16 p13, 1, x0
    //p13拿到isa 通过isa拿到class放入p16寄存器

    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
    //接下来走CacheLookup

        mov x15, x16            // stash the original isa
    LLookupStart\Function:
        // p1 = SEL, p16 = isa
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
        ldr p10, [x16, #CACHE]              // p10 = mask|buckets
        lsr p11, p10, #48           // p11 = mask
        and p10, p10, #0xffffffffffff   // p10 = buckets
        and w12, w1, w11            // x12 = _cmd & mask
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        ldr p11, [x16, #CACHE]          // p11 = mask|buckets
    #if CONFIG_USE_PREOPT_CACHES
    #if __has_feature(ptrauth_calls)
        tbnz    p11, #0, LLookupPreopt\Function
        and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    #else
        and p10, p11, #0x0000fffffffffffe   // p10 = buckets
        tbnz    p11, #0, LLookupPreopt\Function
    #endif
        eor p12, p1, p1, LSR #7
        and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
    #else
        and p10, p11, #0x0000ffffffffffff   // p10 = buckets
        and p12, p1, p11, LSR #48       // x12 = _cmd & mask
    #endif // CONFIG_USE_PREOPT_CACHES
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        ldr p11, [x16, #CACHE]              // p11 = mask|buckets
        and p10, p11, #~0xf         // p10 = buckets
        and p11, p11, #0xf          // p11 = maskShift
        mov p12, #0xffff
        lsr p11, p12, p11           // p11 = mask = 0xffff >> p11
        and p12, p1, p11            // x12 = _cmd & mask
    #else
    #error Unsupported cache mask storage for ARM64.
    #endif
    
        add p13, p10, p12, LSL #(1+PTRSHIFT)
                            // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    
                            // do {
    1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
        cmp p9, p1              //     if (sel != _cmd) {
        b.ne    3f              //         scan more
                            //     } else {
    2:  CacheHit \Mode              // hit:    call or return imp
                            //     }
    3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
        cmp p13, p10            // } while (bucket >= buckets)
        b.hs    1b
    
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
        add p13, p10, w11, UXTW #(1+PTRSHIFT)
                            // p13 = buckets + (mask << 1+PTRSHIFT)
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                            // p13 = buckets + (mask << 1+PTRSHIFT)
                            // see comment about maskZeroBits
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        add p13, p10, p11, LSL #(1+PTRSHIFT)
                            // p13 = buckets + (mask << 1+PTRSHIFT)
    #else
    #error Unsupported cache mask storage for ARM64.
    #endif
        add p12, p10, p12, LSL #(1+PTRSHIFT)
                            // p12 = first probed bucket
    
                            // do {
    4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
        cmp p9, p1              //     if (sel == _cmd)
        b.eq    2b              //         goto hit
        cmp p9, #0              // } while (sel != 0 &&
        ccmp    p13, p12, #0, ne        //     bucket > first_probed)
        b.hi    4b
    

    这里有一个CACHE_MASK_STORAGE,

    #if defined(__arm64__) && __LP64__
    #if TARGET_OS_OSX || TARGET_OS_SIMULATOR
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    #else
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
    #endif
    #elif defined(__arm64__) && !__LP64__
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
    #else
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
    #endif
    

    所以arm64真机是CACHE_MASK_STORAGE_HIGH_16

    p1的类型是SEL,也就是objc_msgSend传进来的参数_cmd, p16是刚才的isa.

    ldr p11, [x16, #CACHE] // 这里p11 = mask|buckets,也就是bucket的首地址,

    这里CONFIG_USE_PREOPT_CACHES是1,并且我们看带签名的部分,也就是__has_feature(ptrauth_calls)是true.

    tbnz p11, #0, LLookupPreopt\Function //判断cache_t存在

    and p10, p11, #0x0000fffffffffff //取出后48位,这部分是buckets,从前面的内容我们知道bucket是连续存储的.

    eor p12, p1, p1, LSR #7 //eor异或,LSR #7 是右移7位,这就是前面说到的cache_hash函数在CONFIG_USE_PREOPT_CACHES为1的时候的算法,是通过这个hash算法获得SEL的hash值,也就是在buckets中的顺位.
    上面说到p1是SEL,所以这个就是p12 = SEL ^ (SEL >> 7),在cache_hash函数里是这么写的value ^= value >> 7.
    and p12, p12, p11, LSR #48 // 不过这里最后还要&上mask,最终得到下标.

    add p13, p10, p12, LSL #(1+PTRSHIFT)// 刚才p12是下标,p10是bucket首地址,PTRSHIFT是3,这里是首地址加上下标左移4位.指向了一个bucket内存.

    1:  ldp p17, p9, [x13], #-BUCKET_SIZE   
        cmp p9, p1          
        b.ne    3f              
    2:  CacheHit \Mode          
    3:  cbz p9, \MissLabelDynamic       
        cmp p13, p10            
        b.hs    1b
    

    这一段是do while,
    ldp p17, p9, [x13], #-BUCKET_SIZE //p13是刚才指向的bucket,-BUCKET_SIZE是一个bucket的大小,意思是指向前面一个bucket.放在p9,
    这一步对标cache_next函数.

    cmp p9, p1 //如果p9不是_cmd.
    b.ne 3f //那么去执行3

    CacheHit \Mode //p9就是_cmd,就执行CacheHit

    cbz p9, \MissLabelDynamic //如果p9是空的,执行MissLabelDynamic函数.

    cmp p13, p10 //如果p13大于p10,也就是说bucket不是第0个.
    b.hs 1b //就执行1,向前移动.

    add p13, p10, p11, LSR #(48 - (1+PTRSHIFT)) //如果向前移动到了首地址,还没匹配到,那就把p13移动到最后一个bucket

    add p12, p10, p12, LSL #(1+PTRSHIFT) //刚才cmp p13, p10是从中间到首地址,现在修改p10,到刚才的p13后面一个,意思就是已经查看过的部分就不再查看了

    4: ldp p17, p9, [x13], #-BUCKET_SIZE // 取出sel

            cmp p9, p1          
        b.eq    2b              
        cmp p9, #0          
        ccmp    p13, p12, #0, ne    
        b.hi    4b
    

    还是do while,如果相等,就执行2, 否则就继续移动,直到走到新的首地址.


    上面使用了和cache_hash和cache_next相同的算法来查找,也就是存的取的方法是一样的,只要存了,那一定取的到,
    如果没存,那么在ldp p17, p9, [x13], #-BUCKET_SIZE这一步,p9就是空的,
    然后cbz p9, \MissLabelDynamic就会跳转到MissLabelDynamic.
    这个MissLabelDynamic是什么呢.

    /*
     * CacheLookup NORMAL|GETIMP|LOOKUP <function> MissLabelDynamic MissLabelConstant
    */
    .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    

    CacheLookup是这么定义的,MissLabelConstant是参数,是一个函数

    回到前面看objc_msgSend调用cacheLookup的地方

    GetClassFromIsa_p16 p13, 1, x0  // p16 = class
    LGetIsaDone:
        // calls imp or objc_msgSend_uncached
        CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
    

    MissLabelConstant传的是objc_msgSend_uncached,所以我们去看这个函数

    STATIC_ENTRY __objc_msgSend_uncached
        UNWIND __objc_msgSend_uncached, FrameWithNoSaves
        MethodTableLookup
        TailCallFunctionPointer x17
    
        END_ENTRY __objc_msgSend_uncached
        STATIC_ENTRY __objc_msgLookup_uncached
        UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
        MethodTableLookup
        ret
        END_ENTRY __objc_msgLookup_uncached
    

    里面执行的是MethodTableLookup

    .macro MethodTableLookup
        
        SAVE_REGS MSGSEND
        // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
        // receiver and selector already in x0 and x1
        mov x2, x16
        mov x3, #3
        bl  _lookUpImpOrForward
    
        // IMP in x0
        mov x17, x0
        RESTORE_REGS MSGSEND
    .endmacro
    

    MethodTableLookup里面执行的是lookUpImpOrForward函数,
    传参是lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER).
    这个函数就是在快速查找失败的时候会执行的,它是C++实现.位于objc-runtime-new.mm


    还是CacheLookup的定义

    #define NORMAL 0
    #define GETIMP 1
    #define LOOKUP 2
    .macro CacheHit
    .if $0 == NORMAL
        TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
    .elseif $0 == GETIMP
        mov p0, p17
        cbz p0, 9f          // don't ptrauth a nil imp
        AuthAndResignAsIMP x0, x10, x1, x16 // authenticate imp and re-sign as IMP
    9:  ret             // return IMP
    .elseif $0 == LOOKUP
        // No nil check for ptrauth: the caller would crash anyway when they
        // jump to a nil IMP. We don't care if that jump also fails ptrauth.
        AuthAndResignAsIMP x17, x10, x1, x16    // authenticate imp and re-sign as IMP
        cmp x16, x15
        cinc    x16, x16, ne            // x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
        ret             // return imp via x17
    .else
    

    mode有三种,NORMAL,GETIMP,和LOOKUP
    objc_msgSend里面传的就是NORMAL.是验证并调用imp

    当传入GETIMP时,就是_cache_getImp这个函数的实现,

    IMP cache_getImp(Class cls, SEL sel, IMP value_on_constant_cache_miss = nil);
    

    这个函数也是频繁的被使用.用于获取imp.

    LOOKUP模式是验证并重签名imp,不会调用.

    慢速查找

    上面讲到,快速查找失败会执行lookUpImpOrForward函数,这个函数位于objc-runtime-new.mm

    enum {
        LOOKUP_INITIALIZE = 1,
        LOOKUP_RESOLVER = 2,
        LOOKUP_NIL = 4,
        LOOKUP_NOCACHE = 8,
    };
    
    NEVER_INLINE
    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
    {
        const IMP forward_imp = (IMP)_objc_msgForward_impcache;
        IMP imp = nil;
        Class curClass;
        runtimeLock.assertUnlocked();
        if (slowpath(!cls->isInitialized())) {
            behavior |= LOOKUP_NOCACHE;
        }
    

    这个方法是在汇编中调用的时候,传参是这样的

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
        // receiver and selector already in x0 and x1
        mov x2, x16
        mov x3, #3
        bl  _lookUpImpOrForward
    

    obj是x0,sel是x1,cls是x2,最后的参数behavior是x3,传的是3.

    首先判断类是否初始化isInitialized,这个条件一般不会进来,如果类没有初始化,behavior = 0011 | 1000 = 1011.

    checkIsKnownClass(cls);
        cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
        runtimeLock.assertLocked();
        curClass = cls;
    

    判断类是否被添加到类表中,也就是存不存在这个类,如果不存在,就会触发断言.
    然后调用realizeAndInitializeIfNeeded_locked初始化类,behavior & LOOKUP_INITIALIZE是0011 & 0001 或者1011 & 0001总之一定是true.

     for (unsigned attempts = unreasonableClassCount();;) {
            if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
    #if CONFIG_USE_PREOPT_CACHES
                imp = cache_getImp(curClass, sel);
                if (imp) goto done_unlock;
                curClass = curClass->cache.preoptFallbackClass();
    #endif
            } else {
                method_t *meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    imp = meth->imp(false);
                    goto done;
                }
                if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                    imp = forward_imp;
                    break;
                }
            }
            if (slowpath(--attempts == 0)) {
                _objc_fatal("Memory corruption in class list.");
            }
            imp = cache_getImp(curClass, sel);
            if (slowpath(imp == forward_imp)) {
                break;
            }
            if (fastpath(imp)) {
                // Found the method in a superclass. Cache it in this class.
                goto done;
            }
        }
    

    这是一个最多执行attempts次的for循环,或者说是无限循环,当(--attempts == 0)时会直接触发断言.
    attempts是一个相当大的数,它是这么实现的

    static unsigned unreasonableClassCount()
    {
        runtimeLock.assertLocked();
        int base = NXCountMapTable(gdb_objc_realized_classes) +
        getPreoptimizedClassUnreasonableCount();
        return (base + 1) * 16;
    }
    

    它是以静态类表的大小加上dyld的类的个数为基础,乘上16得到的一个不真实的数字,因为那个for循环不需要真实的循环次数,只要足够就行.

    isConstantOptimizedCache用于判断是否缓存优化.
    CONFIG_USE_PREOPT_CACHES在arm64并且iOS时是1.
    cache_getImp前面说到了是cahcelookup的getImp mode,用来查找imp.
    如果满足这些条件,就会走快速查找,不过cahcelookup的getImp mode失败了不走lookUpImpOrForward,所以不会循环.

    另一种情况则是去rw中找method_t, getMethodNoSuper_nolock函数是从类本身查找.
    前面curClass首先指向传进来的cls.

    static method_t *
    getMethodNoSuper_nolock(Class cls, SEL sel)
    {
        auto const methods = cls->data()->methods();
        for (auto mlists = methods.beginLists(),
                  end = methods.endLists();
             mlists != end;
             ++mlists)
        {
            method_t *m = search_method_list_inline(*mlists, sel);
            if (m) return m;
        }
        return nil;
    }
    
    

    从class获取methods(),这是一个list_array_tt,可能会有两层结构,内层结构用search_method_list_inline来迭代.
    search_method_list_inline这个函数在上一篇有说明.

    如果从类自己身上没找到的话,curClass = curClass->getSuperclass(),
    curClass会指向父类,顺便判断如果没有父类了,就赋值imp为forward_imp,这个等下再说.

    接下来是前面有提到的for循环最大执行次数.

    然后就是从父类查找,如果找到了就跳到done,没有就继续循环.

        if (slowpath(behavior & LOOKUP_RESOLVER)) {
            behavior ^= LOOKUP_RESOLVER;
            return resolveMethod_locked(inst, sel, cls, behavior);
        }
     done:
        if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
    #if CONFIG_USE_PREOPT_CACHES
            while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
                cls = cls->cache.preoptFallbackClass();
            }
    #endif
            log_and_fill_cache(cls, imp, sel, inst, curClass);
        }
     done_unlock:
        runtimeLock.unlock();
        if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
            return nil;
        }
        return imp;
    

    如果for循环中找到了imp,就会跳转到done或者done_unlock,
    如果没有找到imp,但是从for循环break了,就会走resolveMethod_locked.
    但是它是有条件的,为什么要设置条件呢,因为resolveMethod_locked的流程中也会调用lookUpImpOrForward,
    这行限制改变了behavior,第二次进来的时候就不满足条件了.

    static NEVER_INLINE IMP
    resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
    {
        if (! cls->isMetaClass()) {
            resolveInstanceMethod(inst, sel, cls);
        } 
        else {
            resolveClassMethod(inst, sel, cls);
            if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
                resolveInstanceMethod(inst, sel, cls);
            }
        }
        return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
    }
    

    这个过程叫做动态方法决议.
    做了两件事,
    1.如果是元类就调用resolveClassMethod,如果是类对象就调用resolveInstanceMethod
    2.调用lookUpImpOrForwardTryCache
    lookUpImpOrForwardTryCache就是_lookUpImpTryCache,它里面干了两件事,
    一是快速查找,二是慢速查找.
    为什么要再来次,这是因为步骤1,也就是动态决议,给了程序一个临时添加方法的机会,添加之后,再走一次查找流程.

    static void resolveInstanceMethod(id inst, SEL sel, Class cls)
    {
        SEL resolve_sel = @selector(resolveInstanceMethod:);
        if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
            return;
        }
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(cls, resolve_sel, sel);
        IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
        if (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));
            }
        }
    }
    

    首先获取了一个SEL,resolveInstanceMethod,并且判断class是否实现了这个sel.
    如果实现了,就给class发消息,调用resolveInstanceMethod这个方法.
    然后调用lookUpImpOrNilTryCache查询一次sel是否存在了.

    void newFunc(){
        NSLog(@"新添加的method6");
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        NSLog(@"未知的方法 %@",NSStringFromSelector(sel));
        if([NSStringFromSelector(sel) isEqualToString:@"myMethod6"]){
            class_addMethod(self, sel, (IMP)newFunc, "");
            return true;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    //main.m
    id myObj = [MyClass alloc];
    MyClass *my = (MyClass *)[myObj init];
    [my performSelector:NSSelectorFromString(@"myMethod6")];
    

    写一个demo,可以通过在bool resolved = msg(cls, resolve_sel, sel);断点,
    看到消息发送之后,输出了"未知的方法 myMethod6".
    放开断点输出"新添加的method6".


    在done的位置还调用了log_and_fill_cache函数

    log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
    {
    #if SUPPORT_MESSAGE_LOGGING
        if (slowpath(objcMsgLogEnabled && implementer)) {
            bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                          cls->nameForLogging(),
                                          implementer->nameForLogging(), 
                                          sel);
            if (!cacheIt) return;
        }
    #endif
        cls->cache.insert(sel, imp, receiver);
    }
    
    

    这个函数的作用是给cache_t添加数据.这部分的内容在上一篇.
    上面的logMessageSend是输出方法调用的信息.

    void instrumentObjcMessageSends(BOOL flag)
    

    通过这个函数可以设置是否输出.


    前面提到了一个forward_imp.

    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    

    _objc_msgForward_impcache没有c++的实现,但是可以在.s里找到汇编实现

    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_msgForward,然后__objc_msgForward的实现紧接着在下面,
    调用__objc_forward_handler,最终返回一个x17.
    
    __objc_forward_handler又回到了c++代码
    

    // 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)) ? '+' : '-',
    object_getClassName(self), sel_getName(sel), self);
    }
    void _objc_forward_handler = (void)objc_defaultForwardHandler;

    这就是转发的默认实现,会直接终止程序,错误信息unrecognized selector sent to instance ...
    老朋友了属于是.
    
    这个函数是可以修改的.
    

    void objc_setForwardHandler(void *fwd, void *fwd_stret)
    {
    _objc_forward_handler = fwd;

    if SUPPORT_STRET

    _objc_forward_stret_handler = fwd_stret;
    

    endif

    }

    相关文章

      网友评论

          本文标题:iOS runtime 七: 方法查找与动态决议

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