IMG_5510.JPG

消息补充

  • 方法的本质就是消息发送,其中包含了两个主体
    • 消息接收者
    • 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,奇奇怪怪的!!!!
    1. 2021-06-26 14:55:04.313436+0800 KCObjcBuild[12182:4391368] -[LGPerson saySomething]
    2. (lldb) p *$1
    3. (cache_t) $3 = {
    4. _bucketsAndMaybeMask = {
    5. std::__1::atomic<unsigned long> = {
    6. Value = 4442833280
    7. }
    8. }
    9. = {
    10. = {
    11. _maybeMask = {
    12. std::__1::atomic<unsigned int> = {
    13. Value = 7 ///<< 明明只插入了一次,为什么是7不是3呢?
    14. }
    15. }
    16. _flags = 32808
    17. _occupied = 1
    18. }
    19. _originalPreoptCache = {
    20. std::__1::atomic<preopt_cache_t *> = {
    21. Value = 0x0001802800000007
    22. }
    23. }
    24. }
    25. }

    分析

    第一个开启时capacity必然是4(1<<2), 变为8一定是进行了类似的扩容。扩容一定会走cache_tinsert方法,所以可以在insert方法中插入以下代码循环打印selimp
  1. 调用了[NSObject class]方法
  2. 调用了[NSObject respondsToSelector]方法
  3. 调用了bucket的set方法(如果想要向cache插入bucket(不是cache::insert),必现会调用set方法)
    1. if (sel == @selector(saySomething)) {
    2. bucket_t *kc_b = buckets();
    3. for (unsigned i = 0; i<oldCapacity; i++) {
    4. SEL kc_sel = kc_b[i].sel();
    5. IMP kc_imp = kc_b[i].imp(kc_b, nil);
    6. printf("%p - %p - %p\n", kc_sel, kc_imp, &kc_b[i]);
    7. }
    8. printf("isConstantEmptyCache %p - %u - %u - %u---", kc_b, capacity, newOccupied, oldCapacity);
    9. }
    ```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]

(lldb) po (SEL)0x7fff7c59600c “respondsToSelector:”

(lldb) po (SEL)0x7fff7c595f71 “class”

  1. <a name="XpJBs"></a>
  2. ### 结论
  3. `p`调用方法时,`insert`首先插入了`sel-respondsToSelector`,接着插入了`sel-class`,然后向`bucket`插入值,一定会调用其`set`方法,所以`_maybeMask`达到了`3/4`,进行扩容。
  4. <a name="j9rG0"></a>
  5. ### 扩展----->重点
  6. - 向cache_t插入第一个bucket时,会调用allocBucket
  7. - 流程: `class -> allocBucket -> Bucket.set`
  8. - `allocateBuckets`方法时插入一个`bucket`在末尾,**存**`**cahce_t**`**的首地址(非SEL和IMP)**,作为边界,所以`mask`取值为容量-1.
  9. - 这里也解释了上述开辟内存空间为7的原因。
  10. ![未命名文件 (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)
  11. <a name="VMDb2"></a>
  12. ## insert补充
  13. 以下`do while`循环就是遍向`bucket`插入`sel、imp`,容量大小`m = capacity-1`,起始`bucket`下标位置通过`sel & mask`得到,`arm64`架构由当前(比如1位置)位置向前-1寻找,到0后跳转到mask位置,其它架构为向后+1寻找。
  14. ```objectivec
  15. bucket_t *b = buckets();
  16. mask_t m = capacity - 1; // 4-1=3
  17. mask_t begin = cache_hash(sel, m);
  18. mask_t i = begin;
  19. // Scan for the first unused slot and insert there.
  20. // There is guaranteed to be an empty slot.
  21. do {
  22. if (fastpath(b[i].sel() == 0)) {
  23. incrementOccupied();
  24. b[i].set<Atomic, Encoded>(b, sel, imp, cls());
  25. return;
  26. }
  27. if (b[i].sel() == sel) {
  28. // The entry was added to the cache by some other thread
  29. // before we grabbed the cacheUpdateLock.
  30. return;
  31. }
  32. } while (fastpath((i = cache_next(i, m)) != begin));

CacheLookup汇编(重点)

  • objc_msgSend和CacheLookup都是汇编

    1. .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    2. mov x15, x16 // stash the original isa
    3. LLookupStart\Function:
    4. // p1 = SEL, p16 = isa
    5. ldr p11, [x16, #CACHE] // p11 = mask|buckets Cache = 2 * pointer = 16, p11等于 isa平移16字节 cache_t
    6. #if CONFIG_USE_PREOPT_CACHES
    7. and p10, p11, #0x0000fffffffffffe // p10 = buckets p11平移47位赋值给p10 buckets
    8. and p12, p1, p11, LSR #48 // x12 = _cmd & mask p11位移48位得到mask,&_cmd得到地址 存入P12
    9. #endif // CONFIG_USE_PREOPT_CACHES
    10. // 当前查找的bucket
    11. add p13, p10, p12, LSL #(1+PTRSHIFT)
    12. // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) //((_cmd & mask) << (1+PTRSHIFT))相当于下标转换为16进制地址,方便buckets相加,最后得到b[i] 放入p13,p13即当前要查找的bucket
    13. // do {
    14. 1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket-- p17与p9同时存,一个存imp,一个存sel
    15. cmp p9, p1 // if (sel != _cmd) { 对比传入的sel与当前bucket的sel是否相同, 查到则2 缓存命中
    16. b.ne 3f // scan more
    17. // } else {
    18. 2: CacheHit \Mode // hit: call or return imp
    19. // }
    20. 3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
    21. cmp p13, p10 // } while (bucket >= buckets)
    22. b.hs 1b
  1. 得到类中的cache_t_bucketsAndMaybeMask)地址,赋值给p11x16为isa,位移16字节得到cache_t地址,这里#CACHE`` = 2 * pointer16字节。
  2. _bucketsAndMaybeMask中存储了maskbuckets掩码,mask在高16位,buckets在低48位,通过与运算0x0000fffffffffff将高16位抹零,得到buckets地址赋值给p10
  3. p11, LSR #48 平移48位得到mask_cmd & mask得到bucket的index,p12存储index
  4. p12, LSL #(1+PTRSHIFT) 将下标转换为16地址,再与bucket首地址相加,得到当前bucket地址,p13存储
  5. 开始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的过程,汇编编写

  6. 判断receiver是否存在,不存在直接返回

  7. 通过传入的receiver得到其isa,进而得到其所属于的类,存入p16
  8. class进行内存平移,找到cache (bucketAndMaybeMask存储了mask掩码和buckets掩码)
  9. 通过bucket掩码得到bucket
  10. 通过mask掩码得到mask
  11. insert哈希函数 (mask_t)(value & mask)得到index
  12. 第一次查找的index
  13. bucket首地址+index,地址平移得到整个缓存里面的第几个bucket
  14. bucket(imp sel)
  15. 判断sel 与 _cmd比较,如果相等,则cacheHit,取出imp,进行imp ^ isa,执行callImp
  16. 不相等,拿到bucket—,再次平移 do while循环
  17. 没有找到,objc_msgSend_uncached