美文网首页
objc_msgSend 消息发送之快速查找

objc_msgSend 消息发送之快速查找

作者: 猿人 | 来源:发表于2020-09-26 19:43 被阅读0次

clang源码

源码:
LGTeacher *teacher = [LGTeacher alloc];
[teacher sayHello];
转换之后:
LGTeacher *teacher = ((LGTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGTeacher"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)teacher, sel_registerName("sayHello"));
 

当我们对一个方法调用 进行 clang源码之后 发现 方法的调用 底层实现为 objc_msgSend 函数 即 消息发送

objc_msgSend(id receiver, SEL op, ...)

第一个参数id receiver为消息的接收者
第二个参数SEL op为消息的名称SEL
...为可变参数

objc_msgSend 汇编源码

通过源码进行搜查发现 objc_msgSend 函数

/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd, ...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 * 
 * objc_msgLookup ABI:
 * IMP returned in x17
 * x16 reserved for our use but not used
 *
 ********************************************************************/

为汇编实现 原因
1、 因objc_msgSend 为OC方法核心,调用极其频繁,出于性能考虑,这个函数内部是用汇编来实现。
2、方法参数的动态性,汇编调用函数时传递的参数是不确定的,那么发送消息时,直接调用一个函数就可以发送所有的消息。

源码分析

LGetIsaDone


    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

///判断 receiver是否存才?p0 第一个参数
    cmp p0, #0  // nil check and tagged pointer check
/// 是否支持 taggedPointers对象
#if SUPPORT_TAGGED_POINTERS
/// 是否为空或者小对象类型 (为空 返回 不为空 获取小对象的isa)
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
/// 不是小对象类型
#else
/// 直接返回
    b.eq    LReturnZero
#endif
/// receiver存才 从寄存器x0中获取 isa存入 p13
    ldr p13, [x0]       // p13 = isa
/// 获取 Class
    GetClassFromIsa_p16 p13     // p16 = class
/// 获取 isa完毕
LGetIsaDone:
/// 开启缓存查找流程即快速查找流程
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
///  为nil 直接返回
    b.eq    LReturnZero     // nil check  直接返回
/// 小对象类型 获取 isa
    // 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

首先判断objc_msgSend第一个参数(id receiver为消息的接收者)是否存再,不存在判断是否是支持小对象类型(taggedPointers)的参数 是获取小对象isa 不是 返回空
receiver存在 获取 消息接收者的isa 根据isa获取 class 可能 类对象 可能是元类

CacheLookup NORMAL, _objc_msgSend


/********************************************************************
 *
 * CacheLookup NORMAL|GETIMP|LOOKUP <function>
 *
 * Locate the implementation for a selector in a class method cache.
 *
 * When this is used in a function that doesn't hold the runtime lock,
 * this represents the critical section that may access dead memory.
 * If the kernel causes one of these functions to go down the recovery
 * path, we pretend the lookup failed by jumping the JumpMiss branch.
 *
 * Takes:
 *   x1 = selector
 *   x16 = class to be searched
 *
 * Kills:
 *   x9,x10,x11,x12, x17
 *
 * On exit: (found) calls or returns IMP
 *                  with x16 = class, x17 = IMP
 *          (not found) jumps to LCacheMiss
 *
 ********************************************************************/

 .... 有省略 
LLookupStart$1:
///  isa首地址 平移16字节 获取 cache_t  cache = 高16位 msak + 低48位buckets
    // p1 = SEL, p16 = isa首地址
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
/// arm 64
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
/// 通过chche&掩码将高位mask抹零、得到buckets
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
/// 通过掩码mask 获取 哈希下标
    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

/// p12是获取到的下标,然后逻辑左移4位,再由p10(buckets)平移,得到对应的bucket保存到p12中
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
/// 将p12属性imp 和 sel分别赋值为p17 和 p9
    ldp p17, p9, [x12]      // {imp, sel} = *bucket
/// 比较 bucket的 sel 是否等于 传入的 sel
1:  cmp p9, p1          // if (bucket->sel != _cmd)
/// 不等于 跳到 2
    b.ne    2f//     scan more
/// 如果相同 直接返回 imp 找到了
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
/// 判断当前bucket的sel 是否为0 为0 往下走 不为 0     (cbz    p9, __objc_msgSend_uncached) 开始慢速查找流程
    CheckMiss $0            // miss if bucket->sel == 0
/// 判断 当前的bucket 是否 为 buckets的第一个元素
    cmp p12, p10        // wrap if bucket == buckets
/// 是的话 直接 跳 3
    b.eq    3f
/// 不相等,以x12即当前bucket地址往前移动一个bucket大小为地址读取其中的值,并赋值为p17和p9,同时x12=x12-BUCKET_SIZE
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
/// 跳到 1 循环
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    
///  接上步骤,如果p12和buckets数组第一个相等,需要移动到buckets数组的最后一位
    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.
/// 通过bucket结构体得到imp-sel   将p12属性imp 和 sel分别赋值为p17 和 p9
    ldp p17, p9, [x12]      // {imp, sel} = *bucket
/// 比较 bucket的 sel 是否等于 传入的 sel
1:  cmp p9, p1          // if (bucket->sel != _cmd)
/// 不等于 跳2
    b.ne    2f          //     scan more
/// 等于 命中 直接返回 imp
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
/// 判断当前bucket的sel 是否为0 为0 往下走 不为 0     (cbz    p9, __objc_msgSend_uncached) 开始慢速查找流程
    CheckMiss $0            // miss if bucket->sel == 0
/// 判断 当前的bucket 是否 为 buckets的第一个元素
    cmp p12, p10        // wrap if bucket == buckets
/// 是 跳 3
    b.eq    3f
/// 从最后一个元素往前查找
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
/// 跳出 去往慢速查询流程    b    __objc_msgSend_uncached 
    JumpMiss $0

.endmacro

通过 平移 获取 cache 匹配传进来的 sel 找到 返回imp 没有 进入慢速流程

流程图

objc_msgSend 快速查找流程.jpg

相关文章

网友评论

      本文标题:objc_msgSend 消息发送之快速查找

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