美文网首页
iOS objc_msgSend 流程

iOS objc_msgSend 流程

作者: 卖馍工程师 | 来源:发表于2020-09-19 14:00 被阅读0次

    方法的本质

    在面向对象编程过程中,我们赋予对象一定的特征与能力,日常开发中,我们不可避免的会调用方法,去帮助我们实现复杂逻辑,那么OC调用方法的本质是什么呢?

    我们准备 一个类 Person 继承自 NSObject 。实现方法如下

    @implementation Person
    - (void)study{
        NSLog(@"%s",__func__);
    }
    @end
    

    在main函数中调用方法

    void eat (){
        printf("eat");
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
          
            Person *p = [[Person alloc] init];
           
            [p study];
            
            eat();
        }
        return 0;
    }
    

    为了区分OC方法 与 C函数在底层的区别,这里我们加入一个自定义函数 void eat () 作为对比。

    通过 clang 将main.m编译成 c++文件 clang -rewrite-objc main.m -o main.cpp , 查看编译后的结果如下

    void eat (){
        printf("eat");
    }
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    
            ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("study"));
    
            eat();
        }
        return 0;
    }
    

    可见,对于C函数,编译之后是原样输出的,而OC方法的调用 被编译成下面的形式

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("study"));
    

    OC方法调用的本质就是这样一个发送消息的过程。

    当一个方法被调用的时候,编译器会根据方法调用的类型生成一个底层函数。 调用父类方法生成 objc_msgSendSuper() 函数,非父类方法生成 objc_msgSend() 函数。如果返回值是结构类型,则使用 objc_msgSendSuper_stret 或者 objc_msgSend_stret

    我们以 objc_msgSend() 函数为例

    objc_msgSend(id _Nullable self, SEL _Nonnull _cmd, ...)
    

    它有两个默认参数 id 类型的 selfSEL 类型的 _cmd,其中 self 指向 消息接收者_cmd方法选择器。如果需要传入更多的参数,可以拼接在这两个参数的后面。

    C语言的函数指针直接保存了方法的地址,不同于C语言的是,SEL 保存的是 消息主体,方法 以 SEL 作为索引,通过 SEL 找到 IMP (函数指针)以完成消息的发送。

    那么它是如何通过 SEL 查找 IMP 的呢?

    objc_msgSend ( ) 流程

    通过 SEL 查找 IMP 的过程就是objc_msgSend()消息发送的过程。

    objc_msgSend() 是用 汇编语言 实现的。之所以使用汇编实现,一方面消息发送的过程需要足够的快速,高级语言在执行的时候都是需要翻译成汇编语言,经过编译成被机器识别的二进制文件,使用汇编可以省去这一翻译过程,可以更快速被机器识别;且对于消息的发送,存在很多未知的参数,这有很多不确定性,使用汇编的寄存器要比 C 或者 C++ 表现好的多。

    我们以 arm64架构 为例,看一下 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
     *
     ********************************************************************/
    
    #if SUPPORT_TAGGED_POINTERS
        .data
        .align 3
        .globl _objc_debug_taggedpointer_classes
    _objc_debug_taggedpointer_classes:
        .fill 16, 8, 0
        .globl _objc_debug_taggedpointer_ext_classes
    _objc_debug_taggedpointer_ext_classes:
        .fill 256, 8, 0
    #endif
    
        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
    
    #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
    
    
        ENTRY _objc_msgLookup
        UNWIND _objc_msgLookup, NoFrame
        cmp p0, #0          // nil check and tagged pointer check
    #if SUPPORT_TAGGED_POINTERS
        b.le    LLookup_NilOrTagged //  (MSB tagged pointer looks negative)
    #else
        b.eq    LLookup_Nil
    #endif
        ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13     // p16 = class
    LLookup_GetIsaDone:
        // returns imp
        CacheLookup LOOKUP, _objc_msgLookup
    
    #if SUPPORT_TAGGED_POINTERS
    LLookup_NilOrTagged:
        b.eq    LLookup_Nil // 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    LLookup_GetIsaDone
    
    LLookup_ExtTag: 
        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   LLookup_GetIsaDone
    // SUPPORT_TAGGED_POINTERS
    #endif
    
    LLookup_Nil:
        adrp    x17, __objc_msgNil@PAGE
        add x17, x17, __objc_msgNil@PAGEOFF
        ret
    
        END_ENTRY _objc_msgLookup
    
        
        STATIC_ENTRY __objc_msgNil
    
        // x0 is already zero
        mov x1, #0
        movi    d0, #0
        movi    d1, #0
        movi    d2, #0
        movi    d3, #0
        ret
        
        END_ENTRY __objc_msgNil
    
    
        ENTRY _objc_msgSendSuper
        UNWIND _objc_msgSendSuper, NoFrame
    
        ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
        // calls imp or objc_msgSend_uncached
        CacheLookup NORMAL, _objc_msgSendSuper
    
        END_ENTRY _objc_msgSendSuper
    
        // no _objc_msgLookupSuper
    
        ENTRY _objc_msgSendSuper2
        UNWIND _objc_msgSendSuper2, NoFrame
    
        ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
        ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
        CacheLookup NORMAL, _objc_msgSendSuper2
    
        END_ENTRY _objc_msgSendSuper2
    
        
        ENTRY _objc_msgLookupSuper2
        UNWIND _objc_msgLookupSuper2, NoFrame
    
        ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
        ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
        CacheLookup LOOKUP, _objc_msgLookupSuper2
    
        END_ENTRY _objc_msgLookupSuper2
    
    
    .macro MethodTableLookup
        
        // push frame
        SignLR
        stp fp, lr, [sp, #-16]!
        mov fp, sp
    
        // save parameter registers: x0..x8, q0..q7
        sub sp, sp, #(10*8 + 8*16)
        stp q0, q1, [sp, #(0*16)]
        stp q2, q3, [sp, #(2*16)]
        stp q4, q5, [sp, #(4*16)]
        stp q6, q7, [sp, #(6*16)]
        stp x0, x1, [sp, #(8*16+0*8)]
        stp x2, x3, [sp, #(8*16+2*8)]
        stp x4, x5, [sp, #(8*16+4*8)]
        stp x6, x7, [sp, #(8*16+6*8)]
        str x8,     [sp, #(8*16+8*8)]
    
        // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
        // receiver and selector already in x0 and x1
        mov x2, x16
        mov x3, #3
        bl  _lookUpImpOrForward
    
        // IMP in x0
        mov x17, x0
        
        // restore registers and return
        ldp q0, q1, [sp, #(0*16)]
        ldp q2, q3, [sp, #(2*16)]
        ldp q4, q5, [sp, #(4*16)]
        ldp q6, q7, [sp, #(6*16)]
        ldp x0, x1, [sp, #(8*16+0*8)]
        ldp x2, x3, [sp, #(8*16+2*8)]
        ldp x4, x5, [sp, #(8*16+4*8)]
        ldp x6, x7, [sp, #(8*16+6*8)]
        ldr x8,     [sp, #(8*16+8*8)]
    
        mov sp, fp
        ldp fp, lr, [sp], #16
        AuthenticateLR
    
    .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
    
    
        STATIC_ENTRY __objc_msgLookup_uncached
        UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    
        // THIS IS NOT A CALLABLE C FUNCTION
        // Out-of-band p16 is the class to search
        
        MethodTableLookup
        ret
    
        END_ENTRY __objc_msgLookup_uncached
    
    
        STATIC_ENTRY _cache_getImp
    
        GetClassFromIsa_p16 p0
        CacheLookup GETIMP, _cache_getImp
    
    LGetImpMiss:
        mov p0, #0
        ret
    
        END_ENTRY _cache_getImp
    
    
    

    这是一个很复杂的过程,我们将这部分拆分来看,首先 ENTRY 进入 _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
    

    cmp 是比较函数, p0_objc_msgSend 传入的第一个参数,也就是要响应方法的实例,一般称之为 “消息接收者” ,#0 代表 值 0,这一步就是判断 p0 是否为空。如果支持taggedpointer类型,进入 LNilOrTagged 否则进入 LReturnZero ,结束 _objc_msgSend 流程 。开始 一轮 向 nil 发送消息的流程。如果 p0 不为空,向下执行

        ldr p13, [x0]       // p13 = isa
        GetClassFromIsa_p16 p13     // p16 = class
    

    读取 x0 的首地址 存入 p13。x0 为第一个参数,依然是 消息接收者,不管它是类还是对象,它的第一个成员都是 isa, 所以取x0的首地址,即为 isa, 将 isa 存入 p13 。这里取 isa 的目的是因为 ,不管是对象方法还是类方法,我们都可以通过 isa 的指向 在类或元类的缓存或方法列表中去查找。所以接下来就要通过 isa 取到类或元类。

    GetClassFromIsa_p16 就是获取 类或元类 的过程

    • GetClassFromIsa_p16
    .macro GetClassFromIsa_p16 /* src */
    
    #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
        and p16, $0, #ISA_MASK
    
    #else
        // 32-bit raw isa
        mov p16, $0
    
    #endif
    
    .endmacro
    
    

    这里关注 64位架构(LP64 ), 处理的就是通过 isa & mask 获取到 类或元类 的信息。( 对 isa 的结构不是很清晰的可以 看 这篇博客 看透 isa )。

    LGetIsaDone:
        // calls imp or objc_msgSend_uncached
        CacheLookup NORMAL, _objc_msgSend
    

    获取到 类或元类 的信息之后 ,就可以根据类的存储结构在 cache_t 中 开始查找方法缓存的流程 CacheLookup(这里传入的参数为NORMAL)。

    • CacheLookup
    #define NORMAL 0
    #define GETIMP 1
    #define LOOKUP 2
    
    // CacheHit: x17 = cached IMP, x12 = address of cached IMP, x1 = SEL, x16 = isa
    .macro CacheHit
    .if $0 == NORMAL
        TailCallCachedImp x17, x12, x1, x16 // authenticate and call imp
    .elseif $0 == GETIMP
        mov p0, p17
        cbz p0, 9f          // don't ptrauth a nil imp
        AuthAndResignAsIMP x0, x12, x1, x16 // authenticate imp and re-sign as IMP
    9:  ret             // return IMP
    .elseif $0 == LOOKUP
        // No nil check for ptrauth: the caller would crash anyway when they
        // jump to a nil IMP. We don't care if that jump also fails ptrauth.
        AuthAndResignAsIMP x17, x12, x1, x16    // authenticate imp and re-sign as IMP
        ret             // return imp via x17
    .else
    .abort oops
    .endif
    .endmacro
    
    .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
    
    .macro JumpMiss
    .if $0 == GETIMP
        b   LGetImpMiss
    .elseif $0 == NORMAL
        b   __objc_msgSend_uncached
    .elseif $0 == LOOKUP
        b   __objc_msgLookup_uncached
    .else
    .abort oops
    .endif
    .endmacro
    
    .macro CacheLookup
    
    LLookupStart$1:
    
        // p1 = SEL, p16 = isa
        ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        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
    
    
        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
    
        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
    
    

    这一部分可以结合 类的结构分析 中 对于 cache_t 的分析 一起理解。

    查找缓存 是从 LLookupStart$1开始的。

    LLookupStart$1:
    
        // p1 = SEL, p16 = isa
        ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        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
    
    
        add p12, p10, p12, LSL #(1+PTRSHIFT)
                         // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    
        ldp p17, p9, [x12]      // {imp, sel} = *bucket
    
    类的结构

    在类的结构分析中,我们已知 类的结构排布 为 isa、superclass、cache_t、class_data_bits,这里我们要进行的是查找缓存的流程,缓存的信息是存储在 cache_t 中的。

    #define CACHE            (2 * __SIZEOF_POINTER__)
    ldr p11, [x16, #CACHE]             
    

    x16 是我们上一步中获取到的 类信息,x16 偏移 16字节 就是取到 cache_t 结构,存入 p11 中。

    arm64架构 cache_t 的第一个成员为_maskAndBuckets ,所以这里的 p11 存储的就是 _maskAndBuckets

        and p10, p11, #0x0000ffffffffffff   // p10 = buckets
        and p12, p1, p11, LSR #48       // x12 = _cmd & mask
    
    

    p11_maskAndBuckets ,它的低48位存储 buckets , 高16位存储 mask,#0x0000ffffffffffff 转为2进制如下:

    p11 & #0x0000ffffffffffff,就是取出 _maskAndBuckets 中 的第0 - 47 字节,得到的就是 buckets 赋值给 p10

    之后,将 p11 逻辑右移 48个字节,相当于只保留 mask 的值,与 p1(sel) 进行与运算之后,得到 sel & mask 存入 p12。 在 cache_t 的结构中,mask 为 缓存表 总长度的值 - 1,这里的 sel & mask 得到的值 必定落在 buckets 缓存表的长度 范围内,作为开始遍历查找缓存的 下标值。

        //  注,在arm64架构下,PTRSHIFT = 3 。#define PTRSHIFT 3 
    
        add p12, p10, p12, LSL #(1+PTRSHIFT)
        // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    
        ldp p17, p9, [x12]      // {imp, sel} = *bucket
    

    获取完毕buckets、 mask、 _cmd & mask 的值之后,做了这样一个操作:p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) 。PTRSHIFT 的值在arm64 架构下的值为3 ,所以((_cmd & mask) << (1+PTRSHIFT))得到的结果为16的倍数,具体的倍数取决于 _cmd & mask 计算出的大小。buckets 中存储的是 sel 和 imp ,每一个 bucket 占用16字节,_cmd & mask 作为 buckets 中的初始遍历地址,这一步的操作 其实就是将 buckets 偏移到 初始查询位置,已取到初始查询的 bucket 存入 p12 中。 取到了 初始查询 bucket, 也就取到了 bucket 中的 sel 和 imp ,分别存入 p9p17

    这一部分操作相当于 取出初始要查询的 bucket,假设 cmd & mask 计算出来的值为2 ,那么就从 buckets 的首地址偏移 2 倍的 16个字节。到达 bucket2 的位置,取出 bucket 中的sel 和 imp。

    1:  cmp p9, p1          // if (bucket->sel != _cmd)
        b.ne    2f          //     scan more
        CacheHit $0         // call or return imp
    
    

    接下来比较上一步获取到的 sel(p9 )是否与我们要查找的 sel (p1) 相同,如果相同 通过 CacheHit 将 imp 返回,如果不匹配,跳转到 2:

    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
    
    

    如果上面的结果不匹配,这里将 把比对的样本 进行更换 ,也就是进入循环,更换比较的bucket。p10buckets 散列表的首地址,p12 为当前对比的 bucket ,如果 p10p12 相等,则意味着当前遍历到了 buckets 的首地址,此时执行3:

    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)
    

    p12 = buckets + (mask << 1+PTRSHIFT) 和上面的指针偏移原理是一样的,只不过这次偏移的位置不同,因为mask 是总长度的值-1,所以此次操作是偏移到已存储的 buckets 中的最后一个bucket。

    将当前遍历的位置,移到最后,因为遍历是从_cmd & mask的位置开始,通过 * --bucket 向前遍历,当到达第一个时,则将位置移到最后,继续向前遍历,以确保整个缓存表全部查找。

    通过 将 buckets 的指针 偏移 获取到新的 bucket ,取到sel ,继续回到1:执行对比操作,命中则 CacheHit ,否则继续执行 2:
    如果整个 buckets 遍历结束,依然没有得到匹配信息,则跳转到 CheckMiss

    .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
    

    在 NORMAL 模式下,会进入 __objc_msgSend_uncached 流程。

    • __objc_msgSend_uncached
        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
    

    __objc_msgSend_uncached 中最核心的逻辑就是 MethodTableLookup ,因为我们的缓存并没有命中,这里是开始去方法列表 (methodList) 的查找流程。

    .macro MethodTableLookup
        
        // push frame
        SignLR
        stp fp, lr, [sp, #-16]!
        mov fp, sp
    
        // save parameter registers: x0..x8, q0..q7
        sub sp, sp, #(10*8 + 8*16)
        stp q0, q1, [sp, #(0*16)]
        stp q2, q3, [sp, #(2*16)]
        stp q4, q5, [sp, #(4*16)]
        stp q6, q7, [sp, #(6*16)]
        stp x0, x1, [sp, #(8*16+0*8)]
        stp x2, x3, [sp, #(8*16+2*8)]
        stp x4, x5, [sp, #(8*16+4*8)]
        stp x6, x7, [sp, #(8*16+6*8)]
        str x8,     [sp, #(8*16+8*8)]
    
        // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
        // receiver and selector already in x0 and x1
        mov x2, x16
        mov x3, #3
        bl  _lookUpImpOrForward
    
        // IMP in x0
        mov x17, x0
        
        // restore registers and return
        ldp q0, q1, [sp, #(0*16)]
        ldp q2, q3, [sp, #(2*16)]
        ldp q4, q5, [sp, #(4*16)]
        ldp q6, q7, [sp, #(6*16)]
        ldp x0, x1, [sp, #(8*16+0*8)]
        ldp x2, x3, [sp, #(8*16+2*8)]
        ldp x4, x5, [sp, #(8*16+4*8)]
        ldp x6, x7, [sp, #(8*16+6*8)]
        ldr x8,     [sp, #(8*16+8*8)]
    
        mov sp, fp
        ldp fp, lr, [sp], #16
        AuthenticateLR
    
    .endmacro
    

    在这里,执行查找流程的是 _lookUpImpOrForward 。 至此,objc_msgSend() 的快速查找流程就结束了,接下来进入的 慢速查找流程,也就是通过 isa 与 superclass 的指向,一层层寻找下去,关于慢速查找流程,详见下一篇博客。

    相关文章

      网友评论

          本文标题:iOS objc_msgSend 流程

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