美文网首页
objc_msgSend流程分析

objc_msgSend流程分析

作者: 蓝胖子的梦 | 来源:发表于2020-09-30 02:17 被阅读0次

objc_msgSend流程分析——快速方法查找

前提知识
  1. 编译时(静态):将代码编译成机器能识别的代码的过程。主要是对代码进行语法分析、词法分析,如果有错误就会提示error或warning;clang命令clang -rewrite-objc xxx.m -o xxx.cpp
  2. 运行时(动态):将编译时产生的代码运行在内存的过程。oc也成为动态语言。
    例如:一个函数只在.h声明,而不在.m实现,在调用这个方法的时候,编译器不会报错,但是运行起来就会报错找不到这个方法。
    [xx performSelector:@selector(abc:)];abc方法没有实现也会逃过编译器检查,但是运行时同样会报错。
  3. -w367

方法的本质
  • 方法的本质是消息发送,即objc_msgSend,首先会进行缓存查找,查找不到才会进去class_rw_t的method_list查找。接下来我们来分析缓存查找这个过程。
LGPerson *person = [LGPerson alloc];
[person sayNB];
[person sayHello];
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"));

以上代码我们通过clang命令编译,这就印证了方法的本质就是objc_msgSend消息发送。那我们可以直接调用objc_msgSend吗?答案是可以的。

引入头文件#import <objc/message.h>

-关闭严厉检查机制
objc_msgSend(person,sel_registerName("sayNB"));

编译运行以上代码可以看到和”[person sayNB];“是等同效果的

oc code 编译后 接受者 查找方式
[self sayHello] objc_msgSend self 从类对象开始逐级查找sayHello
[super sayHello] objc_msgSendSuper self 从父类对象开始逐级查找sayHello
  • 上帝视角——objc_msgSend快速查找在objc-msg-arm64.s(只分析arm64架构)


    -w1400
    ///进入方法快速查询 汇编 速度快
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    ///判断p0(消息接受者)是否等于#0(nil)
    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    ///走 tagged pointer 流程
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    ///p0==nil,直接返回 空
    b.eq    LReturnZero
#endif
    ///在寄存器中取x0(获取isa指针) 存在p13
    ldr p13, [x0]       // p13 = isa
    ///通过isa指针获取到类对象  p13(isa) & ISA_MASK,得到class信息
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    /// 缓存查找流程,即快速查找流程
    CacheLookup NORMAL, _objc_msgSend

GetClassFromIsa_p16 此处宏调用

#if SUPPORT_INDEXED_ISA
    // Indexed isa
    mov p16, $0         // 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-bit packed isa
    /// isa & ISA_MASK 得到 类对象信息
    and p16, $0, #ISA_MASK

#else
    // 32-bit raw isa
    mov p16, $0

#endif

紧接着进入CacheLookup 查找 imp

.macro CacheLookup
    //
    // Restart protocol:
    //
    //   As soon as we're past the LLookupStart$1 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$1,
    //   then our PC will be reset to LLookupRecover$1 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
    //
LLookupStart$1:

    // p1 = SEL, p16 = isa
    
    /**
     x16 包含 isa
     CACHE = 16字节 = (2 * __SIZEOF_POINTER__) isa指针8字节 superClass指针8字节
     ldr 读内存命令
     读取x16中平移CACHE(16字节)后的内存,写入到p11中
     */
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    ///cache = mask高16位 + buckets低48位
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    /// p11 mask和buckets p11 & #0x0000ffffffffffff 高16位抹零处理,去掉mask 剩余buckets
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    /// p1 = sel
    /// p11 = cache
    /// LSR = 方法右移 去掉48位,即取到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

    ///p10 = buckets数组首地址,
    ///_cmd & mask 哈希算法 获取sel下标
    ///(1+PTRSHIFT) <<(1+3) 16字节 ,sel=8字节 imp=8字节 一个bucket占用内存大小
    ///p12 首地址+偏移量 = 当前sel所在的bucket
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    /// p17=imp p9=sel
    ldp p17, p9, [x12]      // {imp, sel} = *bucket
    
    ///判断bucket的sel和objc_msgSend的第二个参数_cmd是否相等,如果相等就返回imp
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
    ///bucket的sel和objc_msgSend的_cmd不相等,递归循坏查找缓存,如果循环一圈bucket = buckets时,仍找不到则进入第二次递归
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.
    ///第二次递归循坏查找 仍然查找不到则跳转 __objc_msgSend_uncached 慢速查找流程
    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
objc_msgSend流程分析

相关文章

网友评论

      本文标题:objc_msgSend流程分析

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