前言

我们已经了解了objc_class结构体的内容了,也分析了isa和superclass的功能,我们在来看看objc_class结构体,来开启我们cache的探索之路。

  1. struct objc_class : objc_object {
  2. // Class ISA;
  3. // Class _Nonnull isa OBJC_ISA_AVAILABILITY;
  4. Class superclass;
  5. cache_t cache; // formerly cache pointer and vtable
  6. class_data_bits_t bits;
  7. }

找到cache的地址

首先我们可以通过p/x打印出对象的首地址,我们知道每个指针占8字节,那么isa和superclass都是指针各占8字节,那么我们只要内存平移16字节0x10,就可以找到cache的首地址了。

cache_t结构体源码

  1. struct cache_t {
  2. private:
  3. explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // uintptr_t = long 占8字节
  4. union { // 共用体,共用体的内存大等于成员中最大的成员占内存,所以占8字节
  5. struct {
  6. explicit_atomic<mask_t> _maybeMask; // mask_t = int32 占4字节
  7. #if __LP64__
  8. uint16_t _flags; // 占2字节
  9. #endif
  10. uint16_t _occupied; // 占2字节
  11. };
  12. explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 指针占8字节
  13. };
  14. // 省略……
  15. // occupied++ 插入bucket时调用,缓存计数+1
  16. void incrementOccupied();
  17. // 初始化cache_t
  18. void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
  19. // 获取buckets
  20. struct bucket_t *buckets() const;
  21. // 存储元素个数
  22. mask_t occupied() const;
  23. // 向缓存中插入bucket
  24. void insert(SEL sel, IMP imp, id receiver);
  25. }

_bucketsAndMaybeMask中存储这buckets和mask信息,是在setBucketsAndMask()方法中进行处理
buckets:类似数组,内存连续,存储着缓存的方法
mask:方法查找和插入时的掩码
occupied:记录缓存的方法数量,每次插入新方法时调用incrementOccupied()进行计数+1

insert方法分析

先来看看insert方法源码

  1. void cache_t::insert(SEL sel, IMP imp, id receiver)
  2. {
  3. // 前边忽略,到此处说明需要添加新的缓存
  4. // 计算新的occupied
  5. mask_t newOccupied = occupied() + 1;
  6. // 获取旧的缓存容量
  7. unsigned oldCapacity = capacity(), capacity = oldCapacity;
  8. // 判断是否是创建,如果是创建则进行初始化
  9. if (slowpath(isConstantEmptyCache())) {
  10. // Cache is read-only. Replace it.
  11. if (!capacity) capacity = INIT_CACHE_SIZE;
  12. reallocate(oldCapacity, capacity, /* freeOld */false);
  13. }
  14. // 判断是否需要扩容
  15. else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
  16. // Cache is less than 3/4 or 7/8 full. Use it as-is.
  17. }
  18. #if CACHE_ALLOW_FULL_UTILIZATION
  19. else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
  20. // Allow 100% cache utilization for small buckets. Use it as-is.
  21. }
  22. #endif
  23. // 扩容操作
  24. else {
  25. capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
  26. if (capacity > MAX_CACHE_SIZE) {
  27. capacity = MAX_CACHE_SIZE;
  28. }
  29. // 扩容后需要将旧的存储信息释放掉
  30. reallocate(oldCapacity, capacity, true);
  31. }
  32. bucket_t *b = buckets();
  33. mask_t m = capacity - 1;
  34. mask_t begin = cache_hash(sel, m);
  35. mask_t i = begin;
  36. // hash运算进行存储
  37. do {
  38. if (fastpath(b[i].sel() == 0)) {
  39. incrementOccupied();
  40. b[i].set<Atomic, Encoded>(b, sel, imp, cls());
  41. return;
  42. }
  43. if (b[i].sel() == sel) {
  44. // The entry was added to the cache by some other thread
  45. // before we grabbed the cacheUpdateLock.
  46. return;
  47. }
  48. } while (fastpath((i = cache_next(i, m)) != begin)); // 掩码运算索引
  49. bad_cache(receiver, (SEL)sel);
  50. #endif // !DEBUG_TASK_THREADS
  51. }

初始化创建cache_t

  1. if (!capacity) capacity = INIT_CACHE_SIZE;
  2. reallocate(oldCapacity, capacity, /* freeOld */false);

初始化容量

初始化容量为4(1 << 2 )
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2) = 1 << 2
INIT_CACHE_SIZE_LOG2 = 2

reallocate函数初始化

reallocate实现

  1. void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
  2. {
  3. bucket_t *oldBuckets = buckets();
  4. bucket_t *newBuckets = allocateBuckets(newCapacity);
  5. setBucketsAndMask(newBuckets, newCapacity - 1);
  6. if (freeOld) {
  7. collect_free(oldBuckets, oldCapacity);
  8. }
  9. }
  1. 首先获取旧的buckets;
  2. 再创建一个新的容量的buckets;
  3. 调用setBucketsAndMask函数将buckets和mask关联,mask的值就是容量值-1
  4. 判断是否需要释放旧的缓存,扩容的时候需要释放旧的缓存
  5. 释放旧的缓存

    扩容与collect_free释放旧的缓存

    扩容判断

    通过fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity)判断是否需要扩容
    newOccupied:缓存数量;
    CACHE_END_MARKER:1;
    cache_fill_ratio(capacity):capacity * 3 / 4;
    也就是说,当缓存的元素个数大与容量的3/4时,进行扩容。

    扩容

    capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
    可以看到,扩容时会将容量扩容到原容量的2倍。

    collect_free实现

    1. void cache_t::collect_free(bucket_t *data, mask_t capacity)
    2. {
    3. _garbage_make_room ();
    4. garbage_byte_size += cache_t::bytesForCapacity(capacity);
    5. garbage_refs[garbage_count++] = data;
    6. cache_t::collectNolock(false);
    7. }

    扩容为什么要释放旧的缓存信息
    由于缓存的存储方式采用的是hash表的方式存储,通过cache_hash(sel, m)计算hash表的key,mask的值时容量大小减1,那么扩容后容量变了,所以mask的值也变了,所以需要清空所有缓存重新进行方法缓存。

    Hash存储Key计算

    1. // hash运算进行存储
    2. do {
    3. if (fastpath(b[i].sel() == 0)) {
    4. incrementOccupied();
    5. b[i].set<Atomic, Encoded>(b, sel, imp, cls());
    6. return;
    7. }
    8. if (b[i].sel() == sel) {
    9. // The entry was added to the cache by some other thread
    10. // before we grabbed the cacheUpdateLock.
    11. return;
    12. }
    13. } while (fastpath((i = cache_next(i, m)) != begin)); // 掩码运算索引
  6. 获取buckets

bucket_t *b = buckets();

  1. 通过缓存容量计算出mask

mask_t m = capacity - 1;

  1. 通过sel和mask获取方法的hash值

mask_t begin = cache_hash(sel, m);
mask_t i = begin;

  1. 通过hash值和mask获取key

i = cache_next(i, m)
cache_next内部实质就是将hash值+1后再重新进行掩码运算,直到满足条件后存储缓存,循环结束

  1. fastpath(b[i].sel() == 0

判断buckets中对应的key值是否为空,如果为空的话将该方法插入到对应位置,结束循环,否则hash值加1继续掩码运算

代码查看

先找到最初始的cache信息
image.png
调用test方法后再查看缓存信息
image.png
查看缓存的方法信息,通过bucket_t调用sel查看缓存的方法信息
inline SEL sel() const { return_sel.load(memory_order_relaxed); }
image.png
通过bucket_t调用imp查看函数指针
inline IMP imp (UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls)
image.png