美文网首页iOS
iOS-快速方法查找

iOS-快速方法查找

作者: Summit_yp | 来源:发表于2021-06-28 21:16 被阅读0次

    今天主要是objc_msgSend源码分析:

    
        ENTRY _objc_msgSend //进入_objc_msgSend
        UNWIND _objc_msgSend, NoFrame
    
        cmp p0, #0          // nil check and tagged pointer check  判断消息接收者是否为空 或者 为tagged pointer,
    #if SUPPORT_TAGGED_POINTERS
        b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    #else
        b.eq    LReturnZero  //若对象为空,则直接return 0,这也是空对象调用方法不崩溃的原因
    #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 //结束_objc_msgSend
    

    其实流程看上去很清晰
    1、判断消息接收者是否为空 或者 为tagged pointer
    2、拿到isa,通过isa拿到class
    3、然后在class中的缓存查找方法,找到就直接调用,没找到则走objc_msgSend_uncached

    第二步

    我们先来看看第二步中是怎么通过GetTaggedClass拿到class的吧

    
    //.macro宏定义的意思
    .macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
    
    #if SUPPORT_INDEXED_ISA  //表示 isa_t 中存放的 Class 信息是 Class 的地址,还是一个索引(根据该索引可在类信息表中查找该类结构地址)。经测试,iOS 设备上 SUPPORT_INDEXED_ISA 是 0。
        // Indexed isa
        mov p16, \src           // optimistically set dst = src
        tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f  // done if not non-pointer isa
        // isa in p16 is indexed
        adrp    x10, _objc_indexed_classes@PAGE
        add x10, x10, _objc_indexed_classes@PAGEOFF
        ubfx    p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
        ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
    1:
    
    #elif __LP64__  //64位操作系统
    .if \needs_auth == 0 // needs_auth是入参,传入的1
        mov p16, \src
    .else    //所以会走入else,拿到类对象并放入p16
        // 64-bit packed isa
        ExtractISA p16, \src, \auth_address
    .endif
    #else
        // 32-bit raw isa
        mov p16, \src
    
    #endif
    
    .endmacro
    

    源码中我根据自我理解加上了注释,所以下一步,应该回到ExtractISA中,继续看源码

    .macro ExtractISA
        and    $0, $1, #ISA_MASK//isa& ISA_MASK得到类对象
    .endmacro
    

    就这样拿到类对象啦,好像也不是很复杂,在iOS-isa指向图&类结构(上)中,我们也用这样的方法取到类对象的呀。

    第三步

    接下来就是重点啦,看看他是如何查找缓存的吧,这部分源码偏多,我们分两部分看吧

    第一部分
    .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
    
    /*
     #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
     */
    //由上方宏定义可以得出真机环境下  CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    //这里ifelse过多,我们分级处理一下,这样看着会清晰一点
    
    #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  高16位为mask,低48位为buckets //#define CACHE            (2 * __SIZEOF_POINTER__) 所以cache为16    x16为class,平移16拿到熟悉的cache_t
      #if CONFIG_USE_PREOPT_CACHES   //真机情况下为1 
        #if __has_feature(ptrauth_calls) //a12走这里,判断p11的0号位置,不为0进入LLookupPreopt
        tbnz    p11, #0, LLookupPreopt\Function//LLookupPreopt查找共享缓存
        and p10, p11, #0x0000ffffffffffff   // p10 = buckets  //取低48位的数据也就是取到我们之前探究过的存储在类对象中的cache_t中的,buckets,这是一个结构体,里面有sel,imp
        #else
        and p10, p11, #0x0000fffffffffffe   // p10 = buckets
        tbnz    p11, #0, LLookupPreopt\Function
        #endif
    //下面两句对应cache_hash方法,即p12存的是index
        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
    
    /*
     接着就到这里啦,我们把源码注释提取出来,很明显这就是一个do循环了,找到就调用或返回imp
     // do {
          if (sel != _cmd) {scan more}
          else { hit: call or return imp}
          if (sel == 0) goto Miss;
     } while (bucket >= buckets)
     */
        add p13, p10, p12, LSL #(1+PTRSHIFT)
                            // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) 相当于buckets(i) p13就是要查找的buckets
    
                            // 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
    

    第二部分

    CacheHit源码

    .macro CacheHit
    .if $0 == NORMAL  //找到后,传入的参数是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
    .abort oops
    .endif
    .endmacro
    
    
    
    .macro TailCallCachedImp
        // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
        eor $0, $0, $3  // cached imp &  isa   编码拿到真是imp
        br  $0  //调整执行imp
    .endmacro
    

    流程图

    image.png

    总结

    快速查找流程中有许多通过掩码,位移计算的操作,其实与cache_tinsert方法的流程都是一一对应的,比如hash拿到index,最后通过cache imp & isa拿到imp。感兴趣的朋友可以一一验证一下。

    贴一下大佬的流程图,详细很多

    image.png
    参考:https://www.jianshu.com/p/89ab04a91cbc

    相关文章

      网友评论

        本文标题:iOS-快速方法查找

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