美文网首页
iOS看源码:objc_msgsend消息发送流程01

iOS看源码:objc_msgsend消息发送流程01

作者: FireStroy | 来源:发表于2020-09-19 13:58 被阅读0次

备用知识文章
iOS看源码:cache_t方法缓存

1、一般使用runtime的几中方式

直接在 OC 层进行交互:比如 @selector;
NSObjCRuntime 的:比如NSSelectorFromString 方法;
Runtime API: 比如sel_registerName。

2、消息发送 clang之后的源文件

clang -rewrite-objc main.m -o main.cpp使用clang编译一下main文件输出.cpp文件

int main(int argc, const char * argv[]) {
        MyClass *myClass = [[MyClass alloc]init];
        [myClass say];
    return 0;
}

int main(int argc, const char * argv[]) {
MyClass *myClass = ((MyClass *(*)(id, SEL))(void *)objc_msgSend)((id)((MyClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyClass"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)myClass, sel_registerName("say"));
return 0;
}

可以看到[myClass say];这段代码最后被编译为 id objc_msgSend(id self, SEL _cmd, ...);这种形式
那么 关于objc_msgSend底层实现的源码 在官方给出的文档中(arm64架构) 是以.s文件的汇编源码给出的。

3、objc_msgSend 汇编源码

用汇编写:快 动态性(不确定)
arm-64环境下的objc_msgsend汇编源码

ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    cmp p0, #0    //判断消息接受者是否存在 p0与0做比较 p0存放的是消息接受者
#if SUPPORT_TAGGED_POINTERS  //对消息是否为空的处理
    b.le    LNilOrTagged        
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]    // x0存放的是isa指针 放进p13 p13 = isa
    GetClassFromIsa_p16 p13  //取出isa中的class地址
LGetIsaDone:
    CacheLookup NORMAL, _objc_msgSend  //从缓存中获取imp
//...
LLookup_GetIsaDone:
    // returns imp
    CacheLookup LOOKUP, _objc_msgLookup
.macro 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 环境下 将isa与上mask 获得class地址 放在p16中
    and p16, $0, #ISA_MASK
#else
    // 32-bit raw isa
    mov p16, $0
#endif

上面是消息发送的流程 按照汇编的流程来看 进行参数检查之后进入缓存查找流程


#define CLASS            __SIZEOF_POINTER__ //8
#define CACHE            (2 * __SIZEOF_POINTER__) //2*8=16
#define PTRSIZE 8
#define PTRSHIFT   3 // 1<<PTRSHIFT == PTRSIZE

.macro CacheLookup
LLookupStart$1:
    // p1 = SEL, p16 = isa
    //p16移动16个字节拿到cache p11存放的是cache地址 p11 = mask|buckets
    //(__arm64__ && __LP64__) 环境下 cache_t的首地址存放的是_maskAndBuckets
    ldr p11, [x16, #CACHE]  

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 //真机环境
    and p10, p11, #0x0000ffffffffffff   // &buckets_mask 结果放在p10 = buckets
    //LSR逻辑右移 p11右移48位 把结果(mask)和p1(_cmd = SEL)进行&操作结果放在p12中 得到的是bucket的哈希下标  这个过程还原了方法缓存中下标获取的哈希运算。
    // x12 = _cmd & mask 对应的就是方法缓存时的哈希下标。
    and p12, p1, p11, LSR #48       
#endif

    //LSL 逻辑左移 buckets是数组首地址 每个bucket大小为16字节 p10是数组哈希下标 buckets+(p10 << 4) 作用就是 buckets+index*16 拿到下标处对应的bucket地址 结果放在p12中
    // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
    add p12, p10, p12, LSL #(1+PTRSHIFT)
    //取出对应下标指向的bucket内存地址 依次读出imp sel并放入p17和p9 {imp, sel} = *bucket
    ldp p17, p9, [x12]      
    //比较bucket->sel和_cmd是否相等 相等说明找到了对应的bucket 否则未找到 继续循环
1:  cmp p9, p1      
    //未找到 继续找   
    b.ne    2f          
    //找到了对应的bucket 则返回相应的结果(call or return imp)
    CacheHit $0     
    
2:  // not hit: p12 = not-hit bucket 上面没找到 这里继续找bucket
    CheckMiss $0            // miss if bucket->sel == 0 没有找到缓存 走下一步
    //是p12正在查找的bucket p10是buckets首地址 相等说明p12是第一个元素
    cmp p12, p10        // wrap if bucket == buckets
    //上面结果相同 去3f 不同 跳过下面继续走
    b.eq    3f
    // {imp, sel} = *--bucket 循环查找 每次指针迁移一位
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask 真机下 mask存放在高16位 
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    //p11 = mask|buckets (p11 >> 44) = mask
    // p12 = buckets + (mask << 1+PTRSHIFT)  作用是把指针指向数组中最后一个bucket的位置
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
#else
//...
#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

当查找不到缓存时 就会进入__objc_msgSend_uncached

    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    MethodTableLookup //这里是重点 
    TailCallFunctionPointer x17
    END_ENTRY __objc_msgSend_uncached

这一段的重点在于MethodTableLookup

.macro MethodTableLookup
    //....
    bl  _lookUpImpOrForward 
    //.…
.endmacro

_lookUpImpOrForward这个方法应该非常熟悉
这里 方法的查找就进入了慢速的查找流程
下一篇再继续

相关文章

网友评论

      本文标题:iOS看源码:objc_msgsend消息发送流程01

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