Objc_msgSend流程

 在了解objc_msgSend之前,需要先了解runtime

  Runtime是一套底层纯C语言APIObjective-C代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式。那么什么是运行时呢?

 运行时就是程序已经装载在内存中,与编译时不同的是,运行时在内存中做操作以及判断。

 Objective-C是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段。OC代码的运行不仅需要编译器,还需要运行时系统(Runtime Sytem)来执行编译后的代码。

runtime简析

官方文档介绍# Objective-C Runtime Programming Guide。

调用方式
  • Objective-C Code
  • Framework & Service,例isKindofClass
  • Runtime API,例class_getInstanceSize
下层调用

Compilerllvm

Runtime System Library运行时系统

探索objc_msgSend
  1. 创建一个Command line Tool工程,在main.m文件实现以下内容

    @interface Person : NSObject
    
    - (void)say;
    
    @end
    
    @implementation Person
    
    - (void)say{
        NSLog(@"say");
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            Person *person = [Person alloc];
            [person say];
    
        }
        return 0;
    }
    
    
  2. 通过Clangmain.m文件编译成main.cpp

    注意 ios以及sdk版本取决于本地环境

    clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 -isysroot  /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.0.sdk main.m
    
  3. 搜索int main,找到main函数

    int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
       Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
       ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("say"));
    
    }
    return 0;
    }
    
    • sel_registerName = @selector()= NSSelectorFromString
    • objc_msgSend(),发送的消息(消息的接受者,消息的主题)
通过objc_msgSend实现方法调用
  1. 引入#import 头文件

  2. buildSettings搜索msg_send,设置为false

    objc_msgSend.png
  1. 调用objc_msgSend

    objc_msgSend(person,sel_registerName("say"));
    
  2. 结果调用成功

    调用objc_msgSend.png
通过objc_msgSendSuper调用父类方法
  1. 新增一个Teacher类继承于Person类。

    @interface Teacher : Person
    @end
    @implementation Teacher
    @end
    
  2. Person类新增一个sayHello方法

    - (void)sayHello;
    //...
    -(void)sayHello{
        NSLog(@"sayHello");
    }
    
  3. 初始化结构体objc_super,调用objc_msgSendSuper

    Teacher *teacher = [Teacher alloc];
    struct objc_super send_super;
    send_super.receiver = teacher;
    send_super.super_class = [Person class];
    objc_msgSendSuper(&send_super, sel_registerName("sayHello"));
    
  4. 最终输出结果

    objc_msgSendSuper结果.png
探索objc_msgSend(快速查找流程)
objc_msgSend源码

 在objc源码中找到objc-msg-arm64.s文件,搜索_objc_msgSend,找到ENTRY _objc_msgSend

//---- 消息发送 -- 汇编入口--objc_msgSend主要是拿到接收者的isa信息
    ENTRY _objc_msgSend 
//---- 无窗口
    UNWIND _objc_msgSend, NoFrame 

//---- p0 和空对比,即判断接收者是否存在,其中p0是objc_msgSend的第一个参数-消息接收者receiver
    cmp p0, #0          // nil check and tagged pointer check 
//---- le小于 --支持taggedpointer(小对象类型)的流程
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) 
#else
//---- p0 等于 0 时,直接返回 空
    b.eq    LReturnZero 
#endif 
//---- p0即receiver 肯定存在的流程
//---- 根据对象拿出isa ,即从x0寄存器指向的地址 取出 isa,存入 p13寄存器
    ldr p13, [x0]       // p13 = isa 
//---- 在64位架构下通过 p16 = isa(p13) & ISA_MASK,拿出shiftcls信息,得到class信息
    GetClassFromIsa_p16 p13     // p16 = class 
LGetIsaDone:
    // calls imp or objc_msgSend_uncached 
//---- 如果有isa,走到CacheLookup 即缓存查找流程,也就是所谓的sel-imp快速查找流程
    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
  1. cmp p0 判断对象是否为空;

  2. 如果不支持TAGGED_POINTERS,则返回空;

  3. 根据对象获取isa,最终获取classp16;

  4. 如果有isa,走到CacheLookup 即缓存查找流程,也就是所谓的sel-imp快速查找流程

    CacheLookup NORMAL, _objc_msgSend
    
CacheLookup

在文件中搜索.macro CacheLookup,找到缓存查找的实现

.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

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    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

  1. ldr p11, [x16, #CACHE] // p11 = mask|buckets 平移16位获取Cache
  2. 获取bucketsmask
  3. 比较获取的bucketselobjc_msgSend的第二个参数的_cmd(即p1)是否相等,如果相等调用CacheHit,否则直接跳转至最后一个(查找方式永远是向前查找),继续循环,一直到CheckMiss满足条件。
CheckMiss

Normal时,调用__objc_msgLookup_uncached函数

.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
JumpMiss

Normal时,调用__objc_msgLookup_uncached函数

.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
__objc_msgLookup_uncached

MethodTableLookup 查询方法列表

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
MethodTableLookup

调用_lookUpImpOrForward慢速查找流程

.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
objc_msgSend流程图
objc_msgSend流程分析.png

你可能感兴趣的:(Objc_msgSend流程)