iOS objc_msgSend 流程

方法的本质

在面向对象编程过程中,我们赋予对象一定的特征与能力,日常开发中,我们不可避免的会调用方法,去帮助我们实现复杂逻辑,那么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 的指向,一层层寻找下去,关于慢速查找流程,详见下一篇博客。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335