iOS-快速方法查找

今天主要是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-快速方法查找)