美文网首页iOS底层
IOS底层(十三): 消息流程(一)快速查找

IOS底层(十三): 消息流程(一)快速查找

作者: ShawnAlex | 来源:发表于2021-05-19 20:29 被阅读0次

    OC底层源码/原理合集

    消息传递转发首先肯定要了解个知识点 runtime

    官方文档 Objective-C Runtime Programming Guide

    runtime官方文档

    Runtime

    runtime简称运行时, 是Objective-C语言中非常重要的概念, 它不仅使得OC语言正常运行, 还使得OC语言具有动态的特性。Runtime的运行机制使得OC能够在运行时动态创建类和对象, 进行消息传递转发等

    运行时, 编译时区别
    • 编译时: 其实就是正在编译的时候, 编译器帮你把源代码翻译成机器代码的过程。 主要是对语言进行最基本的检查报错(词法分析, 语法分析等)。例如: 编译代码时候如果有error, warning信息, 都是编译器检查出来的, 这种错误叫编译时错误, 这个过程叫编译时类型检查 或者静态类型检查

      留意: 编译时只是把代码当做文本扫描下, 并未分配内存运行

    • 运行时: 是代码跑起来, 被装载到内存中的过程(内存中判断), 是一个动态阶段。

    例子:
    运行时/编译时例子

    没有sayHello实现方法, 编译时并无报错, 而运行时

    运行时/编译时例子

    可看到由于找不到方法而报错, 这个例子也可以看出运行与编译时的区别


    Runtime调用三种途径

    runtime调用三种途径
    • 通过OC代码,例如: [test sayNB] (Objective-C code)

    • 通过NSObject方法,例如: isKindOfClass, isMemberOf(Framework & Service 接口引入)

    • 通过Runtime API,例如: class_getInstanceSize (Runtime API)

    compiler就是我们了解的编译器, 那一层为编译层即LLVM

    runtime system libarary 就是底层库


    方法本质

    为了查看一下消息流程本质, 我们Clang一下

    clang -rewrite-objc main.m -o main.cpp
    
    cpp代码

    其实我们之前也看到过, 方法的本质就是objc_msgSend消息发送

    验证一下,通过 objc_msgSend方法来完成[person sayNB]的调用,查看其打印是否是一致

    实现准备:

    1、调用objc_msgSend,需要导入头文件#import <objc/message.h>
    2、需要将 target → Build Setting → msg → enable strict checking of obc_msgSend callsYES 改为NO,将严厉的检查机制关掉,否则objc_msgSend的参数会报错

    关闭 enable strict checking of obc_msgSend calls
    TestObj *test = [TestObj alloc];
    [test sayNB];
    objc_msgSend(test, sel_registerName("sayNB"));
    // 方法在底层是消息
    // 消息: 1.消息接受者 2. 消息主体
    
    

    这里通过 objc_msgSend调用方法

    image.png

    enable strict checking of obc_msgSend calls没改的话会报这个错误

    C003A429-F6F5-43C7-9FC8-DF606E8C78B3.png

    可看到两者一致, 即[test sayNB]等同于objc_msgSend(test, sel_registerName("sayNB"));


    调用父类方法

    我们还可以模拟下调用父类方法, 具体通过objc_msgSendSuper实现

    父类SAPerson定义方法sayHello, 子类 SATeacher继承SAPerson, 不设置任何方法

    调用父类方法

    objc_msgSendSuper先看下底层

    OBJC_EXPORT id _Nullable
    objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
        OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    #endif
    

    方法中有两个参数(结构体struct objc_super,方法名sel),其中sel方法名之前讲过, 而结构体类型objc_super看下源码

    /// Specifies the superclass of an instance. 
    struct objc_super {
        /// Specifies an instance of a class.
        __unsafe_unretained _Nonnull id receiver;
    
        /// Specifies the particular superclass of the instance to message. 
    #if !defined(__cplusplus)  &&  !__OBJC2__
        /* For compatibility with old objc-runtime.h header */
        __unsafe_unretained _Nonnull Class class;
    #else
        __unsafe_unretained _Nonnull Class super_class;
    #endif
        /* super_class is the first class to search */
    };
    #endif
    

    objc_super结构体可看出,需要指定接收者receiver 和父类 super_class两个属性

    上面例子我们也可以看出, 子类调用父类方法[teacher sayHello]objc_msgSendSuper(&s, sel_registerName("sayHello"));都执行了父类的中sayHello方法,我们有些疑问 是不是方法调用,首先是在类中查找,如果类中没有找到,会到类的父类中查找?。怎么通过方法名Sel 找到 接下来我们探索下这个问题


    objc_msgSend 快速查找流程分析

    objc4源码中,搜索objc_msgSend,这里留意下objc_msgSend是汇编写的, 而不是C/C++写的。首先所有代码都会被翻译成底层汇编, 然后通过编译时翻译成机器识别语言。

    汇编语言有个特性:

    • 。举个例子: 如果App 1s内有成千上万个方法调用, 汇编语言比用C/C++写少耗时0.01s, 也是会省下很多时间。

    • 动态性(不确定性): C/C++相对于汇编来说构造更加静态, 可能不满足消息发送很多情况

    其实如果用C/C++写消息发送其实也是可以的, 不过相对麻烦, 浪费性能, 所以消息传递这一层面苹果使用汇编写的

    objc_msgSend有一个消息接受者, 消息接受者才能找到你真正的寻根路径。由于方法存在于类/元类里面, 方法查找需要ISA, ISA又存在对象里面无论是实例对象还是类对象。即有: 对象ISA方法(类) → cache(是否有缓存) → methodlist(无缓存, 存在bits中)

    由于我们日常开发的都是架构是arm64(手机端),所以需要在arm64.s后缀的文件中查找objc_msgSend源码实现

    objc-msg-arm64s
    // ---- 消息转发 ----  objc_msgSend主要是拿到接收者的isa信息
    
    //   _objc_msgSend 的 汇编入口  
        ENTRY _objc_msgSend
    //  无窗口
        UNWIND _objc_msgSend, NoFrame
    
    //  p0其实是objc_msgSend的第一个参数-消息接收者receiver
        cmp p0, #0          // nil check and tagged pointer check
    
    //  判空操作, p0与空作对比, 判断接受者是否存在
    //  是否支持taggedpointer(小对象类型)的流程
    #if SUPPORT_TAGGED_POINTERS
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    #else
    // p0 等于 0 返回 空
        b.eq    LReturnZero
    #endif
    // p0非空, 即走接受者存在流程
    // 根据对象取isa(即从x0寄存器指向的地址 取出 isa),其中代码已经提示 p13 = isa
        ldr p13, [x0]       // p13 = isa
    // 通过 p16 = isa(p13) & ISA_MASK,拿出shiftcls信息,得到class信息
    // GetClassFromIsa_p16 详细源码在下方
        GetClassFromIsa_p16 p13, 1, x0  // p16 = class
    LGetIsaDone:
        // calls imp or objc_msgSend_uncached
    // 如果有isa,走CacheLookup (即缓存查找流程),也就是sel-imp快速查找
        CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
    
    #if SUPPORT_TAGGED_POINTERS
    
    // 下边是小对象处理流程
    // 如果是空返回nil, 不为空直接走下面的操作
    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
    
    
    .macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
    
    #if SUPPORT_INDEXED_ISA
        // Indexed isa
    // 将isa的值存入p16寄存器
        mov p16, \src           // optimistically set dst = src
    // 判断是否是 nonpointer isa
        tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f  // done if not non-pointer isa
        // isa in p16 is indexed
    // 将_objc_indexed_classes所在的基址 读入x10寄存器
        adrp    x10, _objc_indexed_classes@PAGE
    // x10 = x10 + _objc_indexed_classes(page中的偏移量), x10基址 根据 偏移量 进行 内存偏移
        add x10, x10, _objc_indexed_classes@PAGEOFF
    // 从p16的第ISA_INDEX_SHIFT位开始,提取 ISA_INDEX_BITS 位到p16寄存器,剩余的高位用0补充
        ubfx    p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
        ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
    1:
    // 用于64位系统
    #elif __LP64__
    .if \needs_auth == 0 // _cache_getImp takes an authed class already
        mov p16, \src
    .else
        // 64-bit packed isa
        ExtractISA p16, \src, \auth_address
    .endif
    #else
    // 用于32位系统
        // 32-bit raw isa
        mov p16, \src
    
    #endif
    
    .endmacro
    

    大致可分为以下几步

    第一步

    判断objc_msgSend方法的第一个参数接收者receiver是否为空, 不为空继续往下走

    • 如果支持tagged pointer(小对象类型), 跳转LNilOrTagged

      • 如果小对象为空, 则返回空(LReturnZero)
      • 如果小对象不为空, 则找到小对象的isa, 接着走下一步
    • 如果即不是tagged pointer(小对象类型), receiver也不为空, 则

      • receiver中取出isa存入p13寄存器
      • 通过 GetClassFromIsa_p16中,arm64架构下通过 isa & ISA_MASK 获取shiftcls位域的类信息,接着走下一步
    第二步

    获取完类信息完毕,进入慢速查找流程CacheLookup NORMAL, 看下CacheLookup 源码

    #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
    
    
    .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
        //
        // Restart protocol:
        //
        //   As soon as we're past the LLookupStart\Function label we may have
        //   loaded an invalid cache pointer or mask.
        //
        //   When task_restartable_ranges_synchronize() is called,
        //   (or when a signal hits us) before we're past LLookupEnd\Function,
        //   then our PC will be reset to LLookupRecover\Function which forcefully
        //   jumps to the cache-miss codepath which have the following
        //   requirements:
        //
        //   GETIMP:
        //     The cache-miss is just returning NULL (setting x0 to 0)
        //
        //   NORMAL and LOOKUP:
        //   - x0 contains the receiver
        //   - x1 contains the selector
        //   - x16 contains the isa
        //   - other registers are set as per calling conventions
        //
    
        mov x15, x16            // stash the original isa
    LLookupStart\Function:
    //  p1 = SEL, p16 = isa
    //  #define CACHE (2 * __SIZEOF_POINTER__), 其中 __SIZEOF_POINTER__表示pointer的大小 ,即 2*8 = 16
    
    //  OSX 系统 或者 64位模拟器
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
        ldr p10, [x16, #CACHE]              // p10 = mask|buckets
    
    // p10 (cache) = mask|buckets  从x16(isa)中平移16字节得到, 存到p10
    // 接下来, 取出mask, 取出buckets
    
    // p10(cache) 右移48位得到p11, p11为mask
    
    // p10(cache)  & 0x0000ffffffffffff , 之后 p10为buckets
    // 这里其实是mask高16位抹零,得到buckets 存入p10寄存器,  即去掉mask,留下buckets
    
    //  x12为_cmd & mask
    
        lsr p11, p10, #48           // p11 = mask
        and p10, p10, #0xffffffffffff   // p10 = buckets
        and w12, w1, w11            // x12 = _cmd & mask
    
    // 其余arm架构 64位机器, 大部分方法与上面类似
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    
    // 真机留意下p11为cache = mask|buckets  
    // 方式照旧 x16(isa)中平移16字节,取出cache 存入p11寄存器
    // isa距离cache 正好16字节:isa(8字节)-superClass(8字节)-cache(mask高16位 + buckets低48位)
    
        ldr p11, [x16, #CACHE]          // p11 = mask|buckets
    
    // 如果配置使用了缓存, 走下边
    #if CONFIG_USE_PREOPT_CACHES
    
    // p11(cache)  & 0x0000ffffffffffff , 之后 得到buckets 存入寄存器p10
    // 跟上面一样, mask高16位抹零,得到buckets 存入p10寄存器,  即去掉mask,留下buckets
    // 下面这里判断是否是真机还是其他系统, 区别仅仅顺序不一样, 都是 p10 = buckets操作
    #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
    
    // 新版的cache取值同样做了一个判断前置是否有cache操作
    // #if CONFIG_USE_PREOPT_CACHES
    // 如果有就平移7位做个异或操作
    //     value ^= value >> 7;
    // #endif
    //     return (mask_t)(value & mask);
    // 下面类似, 其实就是 x12 = _cmd & mask
    // p11(cache)右移48位,得到mask(即p11 存储mask), mask & p1(msgSend的第二个参数 cmd-sel) ,得到sel-imp的下标index(即搜索下标) 存入p12(cache insert写入时的哈希下标计算是 通过 sel & mask,读取时也需要通过这种方式)
    
        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
    
    // 非64位真机 
    #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
    
    // p13是下标 p10是buckets数组首地址,下标 偏移16 得到实际内存的偏移量,通过buckets的首地址偏移,获取bucket存入p13寄存器
    
    // PTRSHIFT是3, 1+3 = 4, 1<<4 即 偏移2^4 = 16
    
    // LSL #(1+PTRSHIFT)-- 实际含义就是得到一个bucket占用的内存大小 -- 相当于mask = occupied -1-- _cmd & mask -- 取余数
      
        add p13, p10, p12, LSL #(1+PTRSHIFT)
                            // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    
                            // do {
    // 从x13(即p13)中取出 bucket 分别将imp和sel 存入 p17(存储imp) 和 p9(存储sel)
    1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    // 比较查询的sel 与 p1(传入的参数cmd)是否相同
        cmp p9, p1              //     if (sel != _cmd) {
    // 不相等跳转3f
        b.ne    3f              //         scan more
                            //     } else {
    // 如果相等, 即找到查询的方法, cacheHit 缓存命中,直接返回imp
    2:  CacheHit \Mode              // hit:    call or return imp
                            //     }
    // 如果sel == 0, 走miss
    3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    // 判断p13(下标对应的bucket) 是否 等于 p10(buckets数组第一个元素)
        cmp p13, p10            // } while (bucket >= buckets)
    // bucket >= buckets 回到1b循环操作
        b.hs    1b
    
        // wrap-around:
        //   p10 = first bucket
        //   p11 = mask (and maybe other bits on LP64)
        //   p12 = _cmd & mask
        //
        // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
        // So stop when we circle back to the first probed bucket
        // rather than when hitting the first bucket again.
        //
        // Note that we might probe the initial bucket twice
        // when the first probed slot is the last entry.
    
    // 令 p13 = buckets + (mask << 1+PTRSHIFT)
    #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 {
    // 向前查找 {imp, sel} = *bucket--
    4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    // 再次比较 `sel(p9)` 与 `cmd(p1)`是否相等
        cmp p9, p1              //     if (sel == _cmd)
    // 相等, 走2b, 即找到查询的方法, `cacheHit` 缓存命中,直接返回`imp`
        b.eq    2b              //         goto hit
    // 如果while (sel != 0 &&bucket > first_probed) 循环走4b方法
        cmp p9, #0              // } while (sel != 0 &&
        ccmp    p13, p12, #0, ne        //     bucket > first_probed)
        b.hi    4b
    
    LLookupEnd\Function:
    LLookupRecover\Function:
        b   \MissLabelDynamic
    
    #if CONFIG_USE_PREOPT_CACHES
    #if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
    #error config unsupported
    #endif
    LLookupPreopt\Function:
    #if __has_feature(ptrauth_calls)
        and p10, p11, #0x007ffffffffffffe   // p10 = buckets
        autdb   x10, x16            // auth as early as possible
    #endif
    
        // x12 = (_cmd - first_shared_cache_sel)
        adrp    x9, _MagicSelRef@PAGE
        ldr p9, [x9, _MagicSelRef@PAGEOFF]
        sub p12, p1, p9
    
        // w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
    #if __has_feature(ptrauth_calls)
        // bits 63..60 of x11 are the number of bits in hash_mask
        // bits 59..55 of x11 is hash_shift
    
        lsr x17, x11, #55           // w17 = (hash_shift, ...)
        lsr w9, w12, w17            // >>= shift
    
        lsr x17, x11, #60           // w17 = mask_bits
        mov x11, #0x7fff
        lsr x11, x11, x17           // p11 = mask (0x7fff >> mask_bits)
        and x9, x9, x11         // &= mask
    #else
        // bits 63..53 of x11 is hash_mask
        // bits 52..48 of x11 is hash_shift
        lsr x17, x11, #48           // w17 = (hash_shift, hash_mask)
        lsr w9, w12, w17            // >>= shift
        and x9, x9, x11, LSR #53        // &=  mask
    #endif
    
        ldr x17, [x10, x9, LSL #3]      // x17 == sel_offs | (imp_offs << 32)
        cmp x12, w17, uxtw
    
    .if \Mode == GETIMP
        b.ne    \MissLabelConstant      // cache miss
        sub x0, x16, x17, LSR #32       // imp = isa - imp_offs
        SignAsImp x0
        ret
    .else
        b.ne    5f              // cache miss
        sub x17, x16, x17, LSR #32      // imp = isa - imp_offs
    .if \Mode == NORMAL
        br  x17
    .elseif \Mode == LOOKUP
        orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
        SignAsImp x17
        ret
    .else
    .abort  unhandled mode \Mode
    .endif
    
    5:  ldursw  x9, [x10, #-8]          // offset -8 is the fallback offset
        add x16, x16, x9            // compute the fallback isa
        b   LLookupStart\Function       // lookup again with a new isa
    .endif
    #endif // CONFIG_USE_PREOPT_CACHES
    
    .endmacro
    
    第三步

    isa首地址平移16字节取cache, 因为在objc_class中, cache与首地址距离正好相差16字节( isa8字节,superclass8字节。

    第四步

    因为cache中高16位存mask,低48位存buckets,即p11 = cache = mask | buckets 。从cache中取出bucketsmask,并由mask根据哈希算法计算出哈希下标

    • cache掩码(0x0000ffffffffffff)& 运算,将高16位mask抹零,得到buckets指针地址,即p10 = buckets

    • cache右移48位,得到mask,即p11 = mask

    • objc_msgSend的参数p1(即第二个参数_cmd)& mask,通过哈希算法,得到需要查找存储sel-impbucket下标index,即p12 = index = _cmd & mask。因为在存储sel-imp时,也是通过同样哈希算法计算哈希下标进行存储,所以读取也需要通过同样的方式读取

    下面是mask_t cache_hash源码

    sel-imp
    #if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST
    #define CONFIG_USE_PREOPT_CACHES 1
    #else
    #define CONFIG_USE_PREOPT_CACHES 0
    #endif
    
    static inline mask_t cache_hash(SEL sel, mask_t mask) 
    {
        uintptr_t value = (uintptr_t)sel;
    #if CONFIG_USE_PREOPT_CACHES
        value ^= value >> 7;
    #endif
        return (mask_t)(value & mask);
    }
    
    第五步

    根据所得的哈希下标indexbuckets首地址,取出哈希下标对应的bucket, 目的是为了进而找到selimp

    结构体bucket_tsel8字节,imp8字节

    通过首地址 + 实际偏移量,获取哈希下标index对应的bucket

    第六步

    根据获取的bucket, 取出imp, sel, 令p17(存储imp)p9(存储sel)

    第七步
    • 比较 bucketsel(p9)cmd(p1)是否相等

      • 相等跳转2b, 即找到查询的方法, cacheHit 缓存命中,直接返回imp

      • 不相等跳转3f
        sel == 0 : goto miss, 走miss方法, 跳转至objc_msgSend_uncached即慢速查找流程
        ② 相等, 判断p13(下标对应的bucket) 是否 等于 p10(buckets数组第一个元素)
        // p10 = first bucket

        ③ 如果bucket > =buckets的第一个元素,回到1b进行递归循环对比 if (sel != _cmd)

        ④ 如果bucket <buckets, 往下走

    • p13 = buckets + (mask << 1+PTRSHIFT)

    • 先做向前查找{imp, sel} = *bucket-- , 再次比较 sel(p9)cmd(p1)是否相等

      • 相等, 走2b, 即找到查询的方法, cacheHit 缓存命中,直接返回imp
      • 不相等 如果while (sel != 0 &&bucket > first_probed) 循环走4b方法

    总结

    由于源码比较复杂, 接下来我们模拟下快速查找

    // 获取当前对象
    id person = 0x10000 //随意举个例子
    // 获取当前isa
    isa_t isa = 0x000000
    // 获取当前class isa → class → cache
    // 因为isa才能获取class, 进而获取cache
    cache_t cache = isa + 16字节
    
    // 进入 CacheLookup 方法
    // 由于arm64下
    cache = mask|buckets
    // buckets 可由cache & 掩码0x0000ffffffffffff 得到
    buckets = cache & 0x0000ffffffffffff
    // mask 可由cache右移48位得到
    mask = cache lsr #48
    // 得到需要查找存储sel imp的bucket下标index, index = _cmd(传入的参数p1) & mask
    index = _cmd & mask  /  (_cmd ^ (_cmd >> 7)) & mask
    // 举个例子: person 有个sayHello方法, 那么我们要找到sayHello, 需要先找到bucket, 因为bucket里面才有sel和imp
    [person sayHello] → imp (cache → bucket → (sel imp))
    
    // 从buckets中遍历查找对应下标的bucket值 (sel + imp = 16, 即下标多少个就平移多少个bucket)
    bucket = buckets + ((_cmd & mask) << (1+PTRSHIFT))
            = buckets +  index * 16
    
    //// 判断bucket里面的sel, 是否匹配cmd
    //if (sel == 0) {
    //    // 走miss方法, 跳转至objc_msgSend_uncached即慢速查找流程
    //    goto miss
    //}else if (bucket.sel == _cmd) {
    //    // 如果相等, 缓存命中
    //    return imp
    //}else {
    //    // 如果不相等, 进入第二层判断
    //    if (bucket == buckets) {
    //        buckets + (mask << 1+PTRSHIFT)
    //    }
    //}
    
    do {
        // 向前查找 {imp, sel} = *bucket-- (bucket = bucket - 1)
        bucket--
        // 判断bucket里面的sel, 是否匹配cmd
        if (bucket.sel == _cmd) {
            // 如果相等, 缓存命中
            return imp
            
        }else {
            // bucket.sel != _cmd 方法
            
            // 走miss方法, 跳转至objc_msgSend_uncached即慢速查找流程
            if (bucket.sel == 0) {
                goto miss
            }
        }
    }while (bucket >= buckets)
    
    //留意下这个相比之前781源码会做2次do while, 分离开了goto miss 以及 sel == 0 方法
    
    // 人为设置bucket为最后一个元素
    bucket =  buckets + (mask << 1+PTRSHIFT)
    
    do {
        // 指针平移, 缓存查找顺序向前查找 {imp, sel} = *bucket-- (bucket = bucket - 1)
        bucket--
        if (bucket.sel == _cmd) {
            // 如果相等, 缓存命中
            // 即 sel = bucket.sel, imp = bucket.imp,
            return imp
            
        }
        
    }while ( sel != 0 && bucket > first_probed(first probed bucket 第一个元素))
    
    
    如果快速查找没有找到就开启慢速查找
    

    相关文章

      网友评论

        本文标题:IOS底层(十三): 消息流程(一)快速查找

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