cache_t之浅析
1.cache_t的结构
-
之前我们有探索过objc_class的结构,分析了属性、变量、方法的存储位置,接下来我们可以探索下cache_t的结构以及作用。
-
以下是来自源码的cache_t的结构体
struct cache_t { struct bucket_t *_buckets; //哈希表 mask_t _mask; //散列表的长度 -1 mask_t _occupied; //当前缓存的方法个数 public: struct bucket_t *buckets(); mask_t mask(); mask_t occupied(); void incrementOccupied(); void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask); void initializeToEmpty(); mask_t capacity(); bool isConstantEmptyCache(); bool canBeFreed(); static size_t bytesForCapacity(uint32_t cap); static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap); void expand(); void reallocate(mask_t oldCapacity, mask_t newCapacity); struct bucket_t * find(cache_key_t key, id receiver); static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn)); };
2.cache_t的功能
cache的功能就是对调用过的方法进行缓存,提高方法的查找速度。
3.为什么要进行方法缓存?
如果一个类有多个父类,那么该类在调用某个方法时, 1.首先会遍历自己类中的方法查找, 2.自己类中查找不到,则会沿着继承链去父类里面遍历查找,如果每一次调用方法都要重复这个步骤,对于系统级别的方法来说,还是比较消耗资源的,所以才出现方法缓存来解决这个问题,每次调用方法时优先去缓存内查找,如果缓存命中,则不用去重复上面的步骤。
4.验证缓存
通过LLDB调试,打印出Test类中cache_t内的信息,可以从图中看出,在Test初始化对象,调用sayHaHa方法后被缓存,哈希表的长度为3,方法缓存的个数为2,分别是sayHaHa、init方法。
- 这时候需要注意的一点是类方法alloc为什么没有被缓存,按照之前我们在类的结构浅析里面提到,对象的方法存在类中,而类方法是以实例方法的形式存在元类中,所以如果要验证类方法的缓存,则需要打印Test元类的cache_t信息,这里不过多做描述。
5.cache_t的缓存策略
上面我们调用了Test对象的sayHaHa方法,接下来我们多调用几个方法看看,
如图,我们明明调用了好几个方法,为什么此时_mask=7,_occupied=1,说明方法缓存有着属于它自己的策略,我们来看看是什么样的一个策略。
mask_t newOccupied = cache->occupied() + 1;
mask_t cache_t::capacity()
{
return mask() ? mask()+1 : 0;
}
从源码可以看出,当前缓存方法的数量+1 > (当前_mask+1)的3/4时会进行扩容,那么为什么扩容以后,occupied会等于1呢?接下来去看看expand()的实现,接下来我们会发现在expand()的方法内调用reallocate(),而这个里面则有我们想要的答案,
从图中可以看出,在这个方法里面不仅设置了newBuckest,mask,还判断清除了旧缓存,所以也就解释了为什么上面Test对象调用init、sayHaHa、testSoHappy、testInstance四个实例方法后,mask=7,occupied=1,而occupied=1的原因是因为在调用testInstance方法时,缓存扩容后,将testInstance方法进行缓存。
===================================================