消息补充
- 方法的本质就是消息发送,其中包含了两个主体
- 消息接收者
- 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的原因。
![未命名文件 (6).jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/21860440/1630759706394-70a89407-23b3-4630-aade-1554682b7bf9.jpeg#clientId=u08d8578f-0d19-4&from=ui&id=u28f69944&margin=%5Bobject%20Object%5D&name=%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6%20%286%29.jpg&originHeight=1513&originWidth=1588&originalType=binary&ratio=1&size=169329&status=done&style=none&taskId=ufedd8a7d-fe2f-4755-85c9-cb9372a0bb4)
<a name="VMDb2"></a>
## insert补充
以下`do while`循环就是遍向`bucket`插入`sel、imp`,容量大小`m = capacity-1`,起始`bucket`下标位置通过`sel & mask`得到,`arm64`架构由当前(比如1位置)位置向前-1寻找,到0后跳转到mask位置,其它架构为向后+1寻找。
```objectivec
bucket_t *b = buckets();
mask_t m = capacity - 1; // 4-1=3
mask_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, MissLabelConstant
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] // p11 = mask|buckets Cache = 2 * pointer = 16, p11等于 isa平移16字节 cache_t
#if CONFIG_USE_PREOPT_CACHES
and p10, p11, #0x0000fffffffffffe // p10 = buckets p11平移47位赋值给p10 buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask p11位移48位得到mask,&_cmd得到地址 存入P12
#endif // CONFIG_USE_PREOPT_CACHES
// 当前查找的bucket
add 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,一个存sel
cmp 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 * pointer
16字节。 _bucketsAndMaybeMask
中存储了mask
和buckets
掩码,mask
在高16位,buckets
在低48位,通过与运算0x0000fffffffffff
将高16位抹零,得到buckets
地址赋值给p10
p11, 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