初探OC底层原理之《类的底层原理结构03-cache_t分析》

一.cache_t数据结构分析

  • 类的底层结构有 isa,superclass,cache,bits, 现在来结节一下cache_t的内部结构
struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; // 4
#if __LP64__
            uint16_t                   _flags;  // 2
#endif
            uint16_t                   _occupied; // 2
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
    };
  • 在cache_t的结构体成员中发现它 是一个联合体,内存大小是16
  • 发现_bucketsAndMaybeMask 存储的是buckets 和Mask
struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
  • 结合上面源码分析得到cache_t 的内部数据结构图


    image.png
  • 验证上面cache_t的内存结构是否正确????下面lldb验证

LGPerson *p  = [LGPerson alloc];
[p saySomething];
(lldb) p/x LGPerson.class
(Class) $0 = 0x00000001000084c0 LGPerson
(lldb) p/x 0x00000001000084d0
(long) $1 = 0x00000001000084d0
(lldb) p/x (cache_t *)0x00000001000084d0
(cache_t *) $2 = 0x00000001000084d0
(lldb) p *$2
(cache_t) $3 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic<unsigned long> = {
      Value = 4311773328
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic<unsigned int> = {
          Value = 3
        }
      }
      _flags = 32808
      _occupied = 1
    }
    _originalPreoptCache = {
      std::__1::atomic<preopt_cache_t *> = {
        Value = 0x0001802800000003
      }
    }
  }
}
(lldb) p $2.buckets()
(bucket_t *) $4 = 0x0000000101007090
  Fix-it applied, fixed expression was: 
    $2->buckets()
(lldb) p $4[1]
(bucket_t) $5 = {
  _sel = {
    std::__1::atomic<objc_selector *> = "" {
      Value = ""
    }
  }
  _imp = {
    std::__1::atomic<unsigned long> = {
      Value = 48832
    }
  }
}
(lldb) p $5.sel()
(SEL) $6 = "saySomething"
(lldb) p $5.imp(nil,LGPerson.class)
(IMP) $7 = 0x0000000100003a00 (KCObjcBuild`-[LGPerson saySomething])
struct bucket_t *cache_t::buckets() const
{
    uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
    return (bucket_t *)(addr & bucketsMask);
}
  • 总结:1.cache_t 里面存储的是 sel 和ipm
    2.cache_t存储方式是哈希表链的方式
    3.bucket_t 获取是根据地址平移获取

二.分析cache_t调用流程分析(脱离源码分析)

@interface LGPerson : NSObject

- (void)say1;
- (void)say2;
- (void)say3;
- (void)say4;
- (void)say5;
- (void)say6;
- (void)say7;

int main(int argc, const char * argv[]) {
@autoreleasepool {
    LGPerson *p  = [LGPerson alloc];
    Class pClass = p.class;  // objc_clas
    [p say1];
    [p say2];
    [p say3];
    [p say4];
    [p say5];
    [p say6];
    [p say7];

    struct kc_objc_class *kc_class = (__bridge struct kc_objc_class *)(pClass);
    NSLog(@"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);

    for (mask_t i = 0; i<kc_class->cache._maybeMask; i++) {
        struct kc_bucket_t bucket = kc_class->cache._bukets[i];
        NSLog(@"%@ - %pf",NSStringFromSelector(bucket._sel),bucket._imp);
    }
    NSLog(@"Hello, World!");
}
  • lldb 打印如下:
2021-06-25 10:08:50.837705+0800 003-cache_t脱离源码环境分析[1354:32771] LGPerson say : -[LGPerson say1]
2021-06-25 10:08:50.838251+0800 003-cache_t脱离源码环境分析[1354:32771] LGPerson say : -[LGPerson say2]
2021-06-25 10:08:50.838414+0800 003-cache_t脱离源码环境分析[1354:32771] LGPerson say : -[LGPerson say3]
2021-06-25 10:08:50.838470+0800 003-cache_t脱离源码环境分析[1354:32771] LGPerson say : -[LGPerson say4]
2021-06-25 10:08:50.838513+0800 003-cache_t脱离源码环境分析[1354:32771] LGPerson say : -[LGPerson say5]
2021-06-25 10:08:50.838555+0800 003-cache_t脱离源码环境分析[1354:32771] LGPerson say : -[LGPerson say6]
2021-06-25 10:08:50.838599+0800 003-cache_t脱离源码环境分析[1354:32771] LGPerson say : -[LGPerson say7]
2021-06-25 10:08:50.838641+0800 003-cache_t脱离源码环境分析[1354:32771] 5 - 7
2021-06-25 10:08:50.838764+0800 003-cache_t脱离源码环境分析[1354:32771] say4 - 0xb850f
2021-06-25 10:08:50.838839+0800 003-cache_t脱离源码环境分析[1354:32771] say6 - 0xb8b0f
2021-06-25 10:08:50.838897+0800 003-cache_t脱离源码环境分析[1354:32771] say3 - 0xb800f
2021-06-25 10:08:50.838939+0800 003-cache_t脱离源码环境分析[1354:32771] (null) - 0x0f
2021-06-25 10:08:50.848600+0800 003-cache_t脱离源码环境分析[1354:32771] say5 - 0xb860f
2021-06-25 10:08:50.848688+0800 003-cache_t脱离源码环境分析[1354:32771] (null) - 0x0f
2021-06-25 10:08:50.848739+0800 003-cache_t脱离源码环境分析[1354:32771] say7 - 0xb8c0f
  • 思考say1 say2 去哪里了?看下源码:
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
   runtimeLock.assertLocked();

   // Never cache before +initialize is done
   if (slowpath(!cls()->isInitialized())) {
       return;
   }

   if (isConstantOptimizedCache()) {
       _objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
                   cls()->nameForLogging());
   }

#if DEBUG_TASK_THREADS
   return _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCK
   mutex_locker_t lock(cacheUpdateLock);
#endif

   ASSERT(sel != 0 && cls()->isInitialized());

   // Use the cache as-is if until we exceed our expected fill ratio.
   mask_t newOccupied = occupied() + 1; // 1+1
   unsigned oldCapacity = capacity(), capacity = oldCapacity;
   if (slowpath(isConstantEmptyCache())) {
       // Cache is read-only. Replace it.
       if (!capacity) capacity = INIT_CACHE_SIZE;//4
       reallocate(oldCapacity, capacity, /* freeOld */false);
   }
   else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
       // Cache is less than 3/4 or 7/8 full. Use it as-is.
   }
#if CACHE_ALLOW_FULL_UTILIZATION
   else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
       // Allow 100% cache utilization for small buckets. Use it as-is.
   }
#endif
   else {// 4*2 = 8
       capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
       if (capacity > MAX_CACHE_SIZE) {
           capacity = MAX_CACHE_SIZE;
       }
       reallocate(oldCapacity, capacity, true);
   }

   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));

   bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
  • 扩容规则 Cache is less than 3/4 or 7/8 full

  • 根据源码解读 buckets 的默认值是 4 当容量达到4分之3时扩容 为 capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; 8;

  • 因为苹果遵循越新越好的原则,在内存处理原来 开辟4的容器抛弃 重新开辟8的容器 所有 里面是 say1 say2 内存回收了

  • buckets 存储是无序的


    1111.png
  • 结合上图可以得知,当buckets开始扩容时,会把原来的数据回收,从新生成一个容器
    *由于存在一个默认的0x1,所以在这里是 <=3/4 或者 <=7/8 扩容。等号的由来就是由于存在0x1这个默认值。这个时候我们自己真实缓存的sel只有两个。在插入第三个前进行了扩容。

  • 根据上面分析得到cache 的整个流程图


    image.png

补充在cache 插入方法查看函数调用栈

(lldb) sbt
frame #0 : 0x1002e2a7b libobjc.A.dylib`cache_t::insert(objc_selector*, void (*)(), objc_object*) + 635
frame #1 : 0x10030064a libobjc.A.dylib`log_and_fill_cache(objc_class*, void (*)(), objc_selector*, objc_object*, objc_class*) + 202
frame #2 : 0x1002ff1d2 libobjc.A.dylib`lookUpImpOrForward + 802
frame #3 : 0x1002d7a9b libobjc.A.dylib`_objc_msgSend_uncached + 75
frame #4 : 0x7fff6881ccd8 libsystem_trace.dylib`os_log_shim_with_CFString + 69
frame #5 : 0x7fff2e55aae4 CoreFoundation`_CFLogvEx3 + 189
frame #6 : 0x7fff30cceee4 Foundation`_NSLogv + 102
frame #7 : 0x7fff30bccef2 Foundation`NSLog + 132
frame #8 : 0x100003658 KCObjcBuild`-[LGPerson saySomething] + 40
frame #9 : 0x100003390 KCObjcBuild`main + 64
  • 发现调用循序是 _objc_msgSend_uncached -> log_and_fill_cache - >cache_t::insert 也是发消息的法式!msgSend 后面文章继续跟新
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容