消息补充
- 方法的本质就是消息发送,其中包含了两个主体
- 消息接收者
- sel + 参数
- 运行objc_msgSend需要注意的点
- 一定要引入头文件
- 包含参数时,Xcode配置Build Setting -> objc_msg -> Enable Strict Checking of (严格检测参数个数)关闭
- 一定要引入头文件
- log_fill_cache -> cache(缓存具备读和写特性)
LLDB补充
在LLDB调试过程中,调用【person say】,会在cache_t中插入一个方法后,_maybeMask应该为capacity-1为3,而实际调试过程中为7,奇奇怪怪的!!!!2021-06-26 14:55:04.313436+0800 KCObjcBuild[12182:4391368] -[LGPerson saySomething](lldb) p *$1(cache_t) $3 = {_bucketsAndMaybeMask = {std::__1::atomic<unsigned long> = {Value = 4442833280}}= {= {_maybeMask = {std::__1::atomic<unsigned int> = {Value = 7 ///<< 明明只插入了一次,为什么是7不是3呢?}}_flags = 32808_occupied = 1}_originalPreoptCache = {std::__1::atomic<preopt_cache_t *> = {Value = 0x0001802800000007}}}}
分析
第一个开启时capacity必然是4(1<<2), 变为8一定是进行了类似的扩容。扩容一定会走cache_t的insert方法,所以可以在insert方法中插入以下代码循环打印sel和imp
- 调用了[NSObject class]方法
- 调用了[NSObject respondsToSelector]方法
- 调用了bucket的set方法(如果想要向cache插入bucket(不是cache::insert),必现会调用set方法)
```objectivec (lldb) p [p saySomething] 0x7fff7c59600c - 0x8650eb0 - 0x108d05d30 ///<< 调用respondsToSelector方法 0x7fff7c595f71 - 0x8650310 - 0x108d05d40 ///<< 调用class方法 0x0 - 0x0 - 0x108d05d50 0x1 - 0x108d05d30 - 0x108d05d60 ///<< allocateBuckets方法插入一个bucket isConstantEmptyCache 0x108d05d30 - 4 - 3 - 4—-2021-06-26 15:33:15.505799+0800 KCObjcBuild[13213:4423553] -[LGPerson saySomething]if (sel == @selector(saySomething)) {bucket_t *kc_b = buckets();for (unsigned i = 0; i<oldCapacity; i++) {SEL kc_sel = kc_b[i].sel();IMP kc_imp = kc_b[i].imp(kc_b, nil);printf("%p - %p - %p\n", kc_sel, kc_imp, &kc_b[i]);}printf("isConstantEmptyCache %p - %u - %u - %u---", kc_b, capacity, newOccupied, oldCapacity);}
(lldb) po (SEL)0x7fff7c59600c “respondsToSelector:”
(lldb) po (SEL)0x7fff7c595f71 “class”
<a name="XpJBs"></a>### 结论`p`调用方法时,`insert`首先插入了`sel-respondsToSelector`,接着插入了`sel-class`,然后向`bucket`插入值,一定会调用其`set`方法,所以`_maybeMask`达到了`3/4`,进行扩容。<a name="j9rG0"></a>### 扩展----->重点- 向cache_t插入第一个bucket时,会调用allocBucket- 流程: `class -> allocBucket -> Bucket.set`- `allocateBuckets`方法时插入一个`bucket`在末尾,**存**`**cahce_t**`**的首地址(非SEL和IMP)**,作为边界,所以`mask`取值为容量-1.- 这里也解释了上述开辟内存空间为7的原因。<a name="VMDb2"></a>## insert补充以下`do while`循环就是遍向`bucket`插入`sel、imp`,容量大小`m = capacity-1`,起始`bucket`下标位置通过`sel & mask`得到,`arm64`架构由当前(比如1位置)位置向前-1寻找,到0后跳转到mask位置,其它架构为向后+1寻找。```objectivecbucket_t *b = buckets();mask_t m = capacity - 1; // 4-1=3mask_t begin = cache_hash(sel, m);mask_t i = begin;// Scan for the first unused slot and insert there.// There is guaranteed to be an empty slot.do {if (fastpath(b[i].sel() == 0)) {incrementOccupied();b[i].set<Atomic, Encoded>(b, sel, imp, cls());return;}if (b[i].sel() == sel) {// The entry was added to the cache by some other thread// before we grabbed the cacheUpdateLock.return;}} while (fastpath((i = cache_next(i, m)) != begin));
CacheLookup汇编(重点)
objc_msgSend和CacheLookup都是汇编
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstantmov x15, x16 // stash the original isaLLookupStart\Function:// p1 = SEL, p16 = isaldr p11, [x16, #CACHE] // p11 = mask|buckets Cache = 2 * pointer = 16, p11等于 isa平移16字节 cache_t#if CONFIG_USE_PREOPT_CACHESand p10, p11, #0x0000fffffffffffe // p10 = buckets p11平移47位赋值给p10 bucketsand p12, p1, p11, LSR #48 // x12 = _cmd & mask p11位移48位得到mask,&_cmd得到地址 存入P12#endif // CONFIG_USE_PREOPT_CACHES// 当前查找的bucketadd p13, p10, p12, LSL #(1+PTRSHIFT)// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) //((_cmd & mask) << (1+PTRSHIFT))相当于下标转换为16进制地址,方便buckets相加,最后得到b[i] 放入p13,p13即当前要查找的bucket// do {1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket-- p17与p9同时存,一个存imp,一个存selcmp p9, p1 // if (sel != _cmd) { 对比传入的sel与当前bucket的sel是否相同, 查到则2 缓存命中b.ne 3f // scan more// } else {2: CacheHit \Mode // hit: call or return imp// }3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;cmp p13, p10 // } while (bucket >= buckets)b.hs 1b
- 得到类中的
cache_t(_bucketsAndMaybeMask)地址,赋值给p11,x16为isa,位移16字节得到cache_t地址,这里#CACHE`` = 2 * pointer16字节。 _bucketsAndMaybeMask中存储了mask和buckets掩码,mask在高16位,buckets在低48位,通过与运算0x0000fffffffffff将高16位抹零,得到buckets地址赋值给p10p11, LSR #48平移48位得到mask,_cmd & mask得到bucket的index,p12存储index- p12, LSL #(1+PTRSHIFT) 将下标转换为16地址,再与bucket首地址相加,得到当前bucket地址,p13存储
开始do while循环,按p13存储的bucket地址取出bucket,将其中的imp、sel分别赋值给p17,p9,然后将p9与传入的sel p1进行比较,相同则缓存命中cacheHit -> callImp,否则跳转3,将当前bucket位置减1,再次进行比较,如遍历完还未找到,调用objc_msgSend_uncached
总结
objc_msgSend(receiver, _cmd) 本质通过sel找imp的过程,汇编编写
判断receiver是否存在,不存在直接返回
- 通过传入的receiver得到其isa,进而得到其所属于的类,存入p16
- class进行内存平移,找到cache (bucketAndMaybeMask存储了mask掩码和buckets掩码)
- 通过bucket掩码得到bucket
- 通过mask掩码得到mask
- insert哈希函数 (mask_t)(value & mask)得到index
- 第一次查找的index
- bucket首地址+index,地址平移得到整个缓存里面的第几个bucket
- bucket(imp sel)
- 判断sel 与 _cmd比较,如果相等,则cacheHit,取出imp,进行imp ^ isa,执行callImp
- 不相等,拿到bucket—,再次平移 do while循环
- 没有找到,objc_msgSend_uncached
