美文网首页
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