[iOS底层]-cache_t分析
[iOS底层]-cache_t分析
在[iOS底层]-类的结构分析中我们讨论了类的结构如下:
同时使用首地址偏移
的办法研究了bits
,同样,今天我们也用此方法来研究一下类的cache
。
cache_t是什么?
下载cache_t分析demo(基于objc4-781源码),使用首地址偏移方法打印cache_t
,偏移量为8+8=16
。
_buckets
: 是一个数组,存放多个bucket_t
结构体指针,bucket_t
存放sel
和imp
._mask
: 地址掩码,和前面我们接触过的ISA_MASK
是类似的。flags
: 标记_occupied
: 缓存的个数,记录缓存数量。
查看cache_t
源码,确实是存在上面4个属性。
struct cache_t { |
CACHE_MASK_STORAGE_OUTLINED
: 模拟器和MacOSCACHE_MASK_STORAGE_HIGH_16
: 真机并且64位CACHE_MASK_STORAGE_LOW_4
: 真机不是64位
_buckets信息打印
_buckets
根据源码可知是explicit_atomic<struct bucket_t *>
类型的一个数组,explicit_atomic
是继承自std::atomic<T>
,主要是确保线程安全。其中泛型struct bucket_t *
才是真正的类型,定义如下。
struct bucket_t { |
在main.m
的19行打一个断点,运行后打印_buckets
信息。
lldb打印信息如下:
- 获取
buckets
需要调用cache_t的buckets()
方法 - 调用
bucket_t的sel()
方法打印sel。 - 调用
bucket_t的imp()
方法打印imp。 - 多个bucket可以使用
p $2.buckets()[0].sel()
来打印,通过数组下标打印多个bucket。
cache_t是如何存储的?
在objc-cache.mm
文件中我们可以找到cache_t::insert
方法,这个方法的实现就是cache_t的存储过程。
根据源码我们可以发现insert方法主要做了三件事:
- 开辟buckets所需内存
- 计算插入位置
- 插入缓存
开辟buckets
下面是cache_t::insert
中关于buckets内存开辟部分的源码
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver) |
- 先计算出当前已占用多少容量
- 读取旧容量,然后判断容量是否够存
- 没有开辟缓存空间,则开辟空间,一次开辟4个
- 如果已占用数量小于等于开辟空间的3/4,不开辟空间
- 如果超过3/4的容量,则在当前内存基础上进行一倍的扩容。
每次开辟空间后都调用了reallocate
方法,这个方法是什么作用呢?
ALWAYS_INLINE |
- 根据newCapacity开辟内存空间
// allocateBuckets方法里开辟内存的代码
bucket_t *newBuckets = (bucket_t *)calloc(cache_t::bytesForCapacity(newCapacity), 1); - 储存
newBuckets
和mask
,mask
为newCapacity - 1
。// 真机状态下,setBucketsAndMask存储代码
_maskAndBuckets.store(((uintptr_t)newMask << maskShift) | (uintptr_t)newBuckets, std::memory_order_relaxed); - 如果要释放旧缓存,则调用
cache_collect_free
方法。 - 本方法作用就是在扩容后重新分配空间,同时会清空以前的缓存。每次扩容后重新开始记录缓存。
扩容算法流程如下图所示


计算插入位置并缓存
ALWAYS_INLINE |
- 通过哈希算法计算出插入位置
- 判断插入位置是否有冲突
- 没有冲突,调用
incrementOccupied();
方法增加占用计数,调用b[i].set<Atomic, Encoded>(sel, imp, cls);
缓存sel和imp
。 - 有冲突,再次进行哈希,计数出新的位置,直到没有冲突为止。
- 没有冲突,调用
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 雷军的博客!