OC底层07:objc_msgSend流程分析

前言

当我们定义一段代码:

        Test *p = [Test alloc];
        [p test1];

它在底层是如何实现的?通过clang进行编译,可以找到:

        Test *p = ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"), sel_registerName("alloc"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("test1"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("test2"));

很明显,OC在底层是通过objc_msgSend传递消息的,第一个参数是接收对象,第二个参数是sel_registerName() (ps:sel_registerName()==@selector()==NSSelectorFromString())
那么objc_msgSend在底层又是如何实现的?

源码解析

因为汇编更快的特性,所以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
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend

    …………
    END_ENTRY _objc_msgSend
1.检查接收者为不为空,为空结束
    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
2.拿到isa
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
3.查找cache中是否有需要的方法
CacheLookup NORMAL, _objc_msgSend

.macro CacheLookup
LLookupStart$1:
    // p1 = SEL, p16 = isa
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
    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
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (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

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    JumpMiss $0
.endmacro
  1. 地址偏移到cache_t在类中的位置 #define CACHE (2 * __SIZEOF_POINTER__) == 16字节
ldr p11, [x16, #CACHE]              // p11 = mask|buckets
  1. cache_t左16位为mask,右48位为buckets
    先得到buckets
//与上0x0000ffffffffffff 得到buckets
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets

因为cache_t分析中提到,方法在cache中进行哈希获取角标进行存储。

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    return (mask_t)(uintptr_t)sel & mask;
}

所以先获取mask再与上sel

and p12, p1, p11, LSR #48       // x12 = _cmd & mask
  1. buckets地址偏移到对应位置开始查找
add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

PTRSHIFT3,左移4位及*16,因为buckets包含selimp,所以一个就是16字节,+ ((_cmd & mask) << (1+PTRSHIFT))则是地址偏移到对应位置。

  1. 循环查找
  • 循环条件:sel != cmd
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
  • 初始条件:当bucket==buckets,及当前bucket为第一个bucket时,跳转到最后一个bucket,逆序查找
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
//mask==总占用-1,mask左移16位,则为总buckets大小
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
  • 如果找到,则返回,没有找到则进入CheckMiss,及进入慢速查找流程_lookUpImpOrForward
.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached

.macro MethodTableLookup
     …………
    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward
      …………

你可能感兴趣的:(OC底层07:objc_msgSend流程分析)