cache_t结构
上一篇详细的介绍了类的结构,Class 是由ISA, superclass, cache, bits组成的结构体.除了cache,都已做了详细介绍,下面即将开始探寻cache_t之旅.
如下是cache_t的内部结构
struct cache_t {
struct bucket_t *_buckets; // 8字节,*即是指针,指针占 8 字节
mask_t _mask; // 4字节,uint32_t mask_t,int 类型 4 字节
mask_t _occupied; // 4字节,同上
}
bucket_t 结构体保存了 imp 以及 key,即 MethodCacheIMP _imp和 cache_key_t, 从中不难看出 cache_t 就是方法缓存
(lldb) x person.class
0x1000012b0: 89 12 00 00 01 80 1d 00 40 f1 af 00 01 00 00 00 ........@.......
0x1000012c0: d0 18 f4 00 01 00 00 00 03 00 00 00 03 00 00 00 ................
(lldb) p (cache_t *)0x1000012c0
(cache_t *) $1 = 0x00000001000012c0
(lldb) p *$1
(cache_t) $2 = {
_buckets = 0x0000000100f418d0
_mask = 3
_occupied = 3
}
(lldb) p $2._buckets
(bucket_t *) $3 = 0x0000000100f418d0
(lldb) p *$3
(bucket_t) $4 = {
_key = 4294971024
_imp = 0x0000000100000c60 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
注意: cache_t第一次不走缓存.即 _imp = 0 ,当二次被调用时才会走缓存.(重启项目,再次运行,也是不走缓存).
下图即为 MachOview 中的体现.

如果多调用几个方法,_mask 就会增长.

cache_t内部是如何变化的,我们带着疑问,探索下去,我把源码的流程总结了一张图,方便理解

由此可见,cache_t内部变化是主要在于cache_fill--填充
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return; //如果取到 imp,就不做缓存填充(判断是否可以命中缓存,通过 sel 找到 imp)
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel); //sel强转数值类型
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1; //通知缓存,占用+1一个位置.来缓存当前方法
mask_t capacity = cache->capacity(); //占用和现在的容量进行对比
if (cache->isConstantEmptyCache()) { //空缓存表
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) { //占位小于容量桶子的3/4,正常存储,否则将扩容
// Cache is less than 3/4 full. Use it as-is.
}
else {
// Cache is too full. Expand it.
cache->expand();//对缓存进行 2 倍扩容.
}
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
bucket_t *bucket = cache->find(key, receiver);//找到脚标对应bucket_t 取值,空的bucket_t说明查找失败,则调用bad_cache.
if (bucket->key() == 0) cache->incrementOccupied();//bucket_t 正确取值后,对应占位_occupied++
bucket->set(key, imp);
}
注意:
Class中的Cache主要是为了在消息发送的过程中,进行方法的缓存,加快调用效率,其中使用了动态扩容的方法,容量达到最大值的3/4时,开始2倍扩容,扩容时会抹除之前的buckets,并且创建新的buckets代替,把最近一次临界点的imp和key缓存进来.