iOS爱上底层-cache_t分析

378 阅读3分钟

cache_t的结构

Class的内部有一个方法缓存,这个方法缓存就是cache_t。chche_t是由_buckets、_mask和_occupied组成。我们看一下_buckets都干了些什么:
我们可以看出,_buckets是由key和imp组成。而imp则是函数地主,key则是方法名.

cache_t的缓存流程

首先看看_mask和_occupied的方法,他们都是返回自己本身,所以重点都在bucket_t。
我们跳进capacity()方法,并搜索capacity(),看下哪里调用了这个方法。
此时我们会进入一个expand(),这个方法的主要功能就是扩容,我们在搜一下expand(),看下哪里调用了这个方法。
代码解析:

  • if (!cls->isInitialized()) return;判断类是否初始化对象,没有就直接返回。
  • if (cache_getImp(cls, sel)) return;先从当前缓存查找imp,找到了就直接返回。
  • cache_t *cache = getCache(cls);从当前的cls里面获取到原来的cache_t的结构体。
  • cache_key_t key = getKey(sel);通过sel获取到他的key,并强转成cache_key_t类型。
  • mask_t newOccupied = cache->occupied() + 1;获取当前类的occupied,并+1。
  • mask_t capacity = cache->capacity();读取当前类的capacity(当前的缓存容量)。
  • cache->isConstantEmptyCache():判断是否第一次,是的话就直接创建。
  • newOccupied <= capacity / 4 * 3:判断newOccupied是否小于等于capacity的4分之3,是的话说明都做,不是的话就进入else,进行扩容。进入expand()。
  • bucket_t *bucket = cache->find(key, receiver);通过key和传进来的receiver,找到bucket。
  • if (bucket->key() == 0) cache->incrementOccupied();判断当前的bucket的key是否等于0,是的话occupied+1。
  • bucket->set(key, imp);把key和imp放入bucket。

扩容代码解析:

  • uint32_t oldCapacity = capacity();获取到之前的缓存容量。
  • uint32_t newCapacity = oldCapacity ? oldCapacity2 : INIT_CACHE_SIZE;判断之前的容量是否有值,有的话就2,没有的话就初始化4容量
  • reallocate(oldCapacity, newCapacity);创建新的缓存,并清除旧的缓存。

我们进入reallocate看下详细源码:

验证cache_t的流程

main里面的代码:

验证方法是否加入缓存
当断点运行到Second方法的时候,我们可以在cache_fill_nolock打上一个断点,然后运行至断点处,我们就会看见First已经加入缓存,并且mask为3,occupied为1。当然也可以用LLDB看:
验证扩容
上面我们已经扩容成功了,但是buckts什么都没有,这时候我们就要看reallocate里面做了什么。
我们可以看见,赋新值得时候,newCapacity-1了,主要是内存他不能存满,所以我们在LLDB里面看见的marks是7的原因,而buckts什么都没有的原因是freeOld里面,里面释放旧的cache_t的时候,并没有将旧的cache_t赋值给新的cache_t,所以扩容厚我们发现buckets什么都没有。

元类cache_t的验证

总结

1、OC的实例方法,是缓存在cache_t中的,类方法,则是缓存在元类中(步骤是一样的,只是要去元类中查找)。
2、cache的本质是一个Hash表
3、如果mask满了,就会进行扩容,扩容为原来的2倍-1
4、扩容的时候,会将原来的缓存清除,并不会将原来的缓存添加到新的缓存中。因为此时读写缓存不安全,同时也是为了效率最大化