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

参考://www.greatytc.com/p/89ab04a91cbc

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容