iOS底层原理 09 : objc_msgSend快速查找流程

2020-09-21  本文已影响0人  smooth_lgh

1. Runtime

Objective-C Runtime Programming Guide

Runtime就是使用C,C++和汇编混合而成的,为OC提供运行时特性的一套机制。

2.Runtime底层

首先通过Clang编译main.c文件

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        [person sayNB];
        [person sayHello];
    }
    return 0;
}

在mian.cpp文件中,我们可以看到:
1.OC层的方法调用会编译成objc_msgSend(消息接收者,sel_registerName("XXX"))
2.所以@selector(XXX)等价于sel_registerName("XXX")
3.[person sayNB]等价于objc_msgSend(person,sel_registerName("sayNB"))

         LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
         ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
         ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));

3.objc_msgSend的快速查找

通过上一篇对cache_t的结构分析,我们知道(_sel,_imp)是存储在cache_t下的buckets的哈希表中

cache_t内部结构图.png

objc_msgSend会先从内存中查找,通过汇编快速查询到IMP,下面我们去源码里面看它的查找过程

我们通过源码找到objc-msg-arm64.s文件里面

// enter  开始进入 _objc_msgSend
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
       // 获取isa
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
        //  从缓存里面获取imp
    CacheLookup NORMAL, _objc_msgSend

#if SUPPORT_TAGGED_POINTERS
// 小对象类型
LNilOrTagged:
    b.eq    LReturnZero     // nil check

    // tagged
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr x16, [x10, x11, LSL #3]
    adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
    add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
    cmp x10, x16
    b.ne    LGetIsaDone

    // ext tagged
    adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
    ubfx    x11, x0, #52, #8
    ldr x16, [x10, x11, LSL #3]
    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

CacheLookup的实现:

.macro CacheLookup
LLookupStart$1:

    // p1 = SEL, p16 = isa
       // #define CACHE  
       // 所以p11指向了cache_t的首地址
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// p10 = p11 &  0x0000ffffffffffff
// p10 指向cache_t下的buckets
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    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

// 内存平移
// (1+PTRSHIFT):(1+3)
// p12 = buckets + ((_cmd & mask) << (4))
// 为什么左移4位, 因为bucket存了(SEL,IMP) ,恰好总共16字节。
// 所以得到buckets[1]
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
       // 先前查找
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p12, p12, p11, LSL #(1+PTRSHIFT)
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    JumpMiss $0

.endmacro

大致过程:
1.首先判断方法接受者p0存不存在.
2.通过对象的isa获取class
3.通过类的指针平移16字节,获取到cache.
4.通过cache,通过计算得到buckets,mask以及index
5.通过下标index获取buckets哈希表下标为index的值bucket_t
6.将bucket.sel与_cmd对比,如果一致,则返回bucket.imp

通过对汇编源码的分析,得到通过汇编在内存中查找IMP的流程

objc_msgSend快速查找流程分析.png
上一篇下一篇

猜你喜欢

热点阅读