iOS-底层原理08-msgSend(cache_t)

《iOS底层原理文章汇总》
运行时感受,通过clang底层编译
编译前的代码 #import @interface DCPerson :NSObject -(void)sayNB; -(void)sayHello; @end@implementation DCPerson -(void)sayNB{ NSLog(@"%s",__func__); } -(void)sayHello{ NSLog(@"%s",__func__); } @endint main(int argc, const char * argv[]) { @autoreleasepool { DCPerson *person = [DCPerson alloc]; [person sayNB]; [person sayHello]; NSLog(@"Hello, World!"); } return 0; }编译后的代码 int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; DCPerson *person = ((DCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("DCPerson"), 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")); NSLog((NSString *)&__NSConstantStringImpl__var_folders_0d_yy9dm1g92sjdnrzk1pn6cgf80000gn_T_main_9aaf89_mi_2); } return 0; }

  • 1.调用sayNB方法进行等价互换objc_msgSend(person,sel_registerName("sayNB"));
objc_msgSend(person,sel_registerName("sayNB"));

  • 2.使用objc_msgSendSuper
@interface Anthropoid : NSObject -(void)sayHello; @end@implementation Anthropoid -(void)sayHello{ NSLog(@"%s",__func__); } @end@interface DCPerson :Anthropoid -(void)sayNB; @end@implementation DCPerson -(void)sayNB{ NSLog(@"%s",__func__); } @end//消息的接受者还是自己 - 父类 - 直接找父类 struct objc_super lgsuper; lgsuper.receiver = person; lgsuper.super_class = [Anthropoid class]; objc_msgSendSuper(&lgsuper, sel_registerName("sayHello")); //输出 -[Anthropoid sayHello]

OC 方法 - 消息 (sel imp) sel -> imp -> 内容
iOS-底层原理08-msgSend(cache_t)
文章图片
isa流程图.png
iOS-底层原理08-msgSend(cache_t)
文章图片
objc_msgSend流程分析.png 对象 - ISA - 方法(类) - cache_t - method_list(bits)
伪代码实现
[person sayHello] -> imp (cache -> bucket (sel imp)) //获取当前的对象 id person = 0x100000 //获取isa isa_t isa = 0x000000 //isa -> class -> cache cache_t cache = isa + 16字节//arm64 //mask|buckets 在一起的 buckets = cache & 0x0000ffffffffffff //获取mask mask = cache LSR #48 //下标 = mask * & sel index = mask & p1 //bucket 从buckets遍历的开始(起始查询的bucket) bucket = buckets + index * 16 (sel imp = 16)int count = 0; //CheckMiss $0do{ if(count == 2) goto CheckMiss if(bucket == buckets){//进入第二层判断 //bucket == 第一个元素 //bucket人为设置到最后一个元素 bucket = buckets + mask * 16; count++; } // {imp,sel} = * --bucket //缓存的查找的顺序是:向前查找 bucket --; imp = bucket.imp; sel = bucket.sel; }while(bucket.sel != _cmd)//bucket里面的sel是否匹配_cmd//CacheHit $0 return imp; CheckMiss: CheckMiss(normal)

伪代码自己完善下???
buckets@2x.png 一个bucket中存储了16个字节的数据,因为sel和imp分别占用8字节 p12 = buckets + (mask << 1 + PTRSHIFT)表示直接索引到最后一个元素
/* //伪代码实现 LLookupStart$1:// p1 = SEL, p16 = isa ldrp11, [x16, #CACHE]// p11 = mask|buckets #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 andp10, p11, #0x0000ffffffffffff// p10 = buckets andp12, p1, p11, LSR #48// x12 = _cmd & mask #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 andp10, p11, #~0xf// p10 = buckets andp11, p11, #0xf// p11 = maskShift movp12, #0xffff lsrp11, p12, p11// p11 = mask = 0xffff >> p11 andp12, p1, p11// x12 = _cmd & mask #else #error Unsupported cache mask storage for ARM64. #endifaddp12, p10, p12, LSL #(1+PTRSHIFT) // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))ldpp17, p9, [x12]// {imp, sel} = *bucket 1:cmpp9, p1// if (bucket->sel != _cmd) b.ne2f//scan more CacheHit $0// call or return imp 2:// not hit: p12 = not-hit bucket CheckMiss $0// miss if bucket->sel == 0 cmpp12, p10// wrap if bucket == buckets b.eq3f ldpp17, p9, [x12, #-BUCKET_SIZE]!// {imp, sel} = *--bucket b1b// loop 3:// wrap: p12 = first bucket, w11 = mask #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 addp12, p12, p11, LSR #(48 - (1+PTRSHIFT)) // p12 = buckets + (mask << 1+PTRSHIFT) #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 addp12, 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.ldpp17, p9, [x12]// {imp, sel} = *bucket 1:cmpp9, p1// if (bucket->sel != _cmd) b.ne2f//scan more CacheHit $0// call or return imp 2:// not hit: p12 = not-hit bucket CheckMiss $0// miss if bucket->sel == 0 cmpp12, p10// wrap if bucket == buckets b.eq3f ldpp17, p9, [x12, #-BUCKET_SIZE]!// {imp, sel} = *--bucket b1b// loop LLookupEnd$1: LLookupRecover$1: 3:// double wrap JumpMiss $0 */[person sayHello] -> imp(cache -> bucket(sel,imp)) //获取当前对象 id person = 0x1000000 //获取isa isa = 0x00000001 //isa -> class -> cache cache_t cache = isa + 16字节 //arm64 //mask|buckets在一起的 buckets = cache & 0x0000ffffffffffff //获取mask mask = cache LSR(逻辑右移) #48位 //下标 = mask & sel index = mask & p1 //获取单个的bucket,从buckets,遍历的开始(起始查询的bucket) bucket = buckets + ((_cmd & mask) << (1+PTRSHIFT)) bucket = buckets + index * 16(sel,imp分别8字节) while(bucket.sel != _cmd){//bucket里面的sel是否匹配_cmd //不满足,没有找到_cmd进入第二层判断 if(bucket == buckets){ //bucket == 第一个元素 //bucket 人为设置到最后一个元素 p12 = buckets + (mask << 1+PTRSHIFT) bucket = buckets + mask * 16(从后往前查找,减减(--操作)) }//{imp,sel} = *--bucket,缓存查找的顺序:向前查找 bucket--; imp = bucket.imp; sel = bucket.sel; } return bucket.imp; //跳出循环即找到缓存中的方法,返回bucket.imp goto CheckMiss:CheckMiss(normal)

消息发送到汇编之后,进入入口函数ENTRY,之后如何找到当前的类?
ENTRY _objc_msgSend,之后通过ISA找到类
getClassFromIsa@2x.png
ldr p13, [x0]// p13 = isa GetClassFromIsa_p16 p13// p16 = class

iOS-底层原理08-msgSend(cache_t)
文章图片
isa得到类@2x.png 【iOS-底层原理08-msgSend(cache_t)】查看GetClassFromIsa_p16,$0表示当前对象传过来的第一个参数为isa,和ISA_MASK(掩码)进行与运算,放到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 adrpx10, _objc_indexed_classes@PAGE add x10, x10, _objc_indexed_classes@PAGEOFF ubfxp16, 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

LP64@2x.png CacheLookup:若bucket==buckets,表示是第一个元素,则到3f,将bucket移到最后一个元素,比较当前是否是要查找的_cmd == bucket.imp,若是,缓存命中直接返回,若不是,则跳2f,先判断是否遗失(是否为空,sel是否存在(bucket.sel == 0)),若不存在且bucket == buckets,即当前bucket为第一个元素(顶层),则跳JumpMiss $0,表示遗失了对象的方法即没找到,若bucket != buckets,则向前继续查找,进行bucket--,往前查找。
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.ne2f//scan more CacheHit $0// call or return imp2:// not hit: p12 = not-hit bucket CheckMiss $0// miss if bucket->sel == 0 cmp p12, p10// wrap if bucket == buckets b.eq3f ldp p17, p9, [x12, #-BUCKET_SIZE]!// {imp, sel} = *--bucket b1b// loop3:// 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.ne2f//scan more CacheHit $0// call or return imp2:// not hit: p12 = not-hit bucket CheckMiss $0// miss if bucket->sel == 0 cmp p12, p10// wrap if bucket == buckets b.eq3f ldp p17, p9, [x12, #-BUCKET_SIZE]!// {imp, sel} = *--bucket b1b// loopLLookupEnd$1: LLookupRecover$1: 3:// double wrap JumpMiss $0.endmacro

    推荐阅读