IOS底层原理Cache_t流程分析

1,183 阅读4分钟

前言

什么是cache,缓存,缓存什么?怎么缓存?cache的结构是什么?缓存流程?带着这些疑问开启本文。

准备工作

cache_t 结构

private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
    
    
    
    void incrementOccupied();

    void setBucketsAndMask(**struct** bucket_t *newBuckets, mask_t newMask);

    void reallocate(mask_t oldCapacity, mask_t newCapacity, **bool** freeOld);

    void collect_free(bucket_t *oldBuckets, mask_t oldCapacity);

    static bucket_t *emptyBuckets();

    static bucket_t *allocateBuckets(mask_t newCapacity);

    static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
    
    struct bucket_t *buckets() const;

    static struct bucket_t * endMarker(**struct** bucket_t *b, uint32_t cap);
  

根据这个结构我们大概可以看出来buckets很重要,emptyBuckets,struct bucket_t *buckets(),static bucket_t *allocateBuckets(mask_t newCapacity)都可以看出来在操作buckets.我们走进去看下结构。

bucket_t的结构

struct bucket_t {

private:

#if __arm64__

    explicit_atomic<uintptr_t> _imp;

    explicit_atomic<SEL> _sel;

#else

    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
    }    

sel,imp 这不就是方法编号和函数指针地址吗!原来bucket里面存放了sel和imp.

  • cache 缓存的是方法

bucket_t的结构图解

1.png

lldb调试

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p  = [LGPerson alloc];
        Class pClass = [LGPerson **class**];
        NSLog(@"%@",pClass);     
    }
    return 0;
}

2.png

  • 类的首地址平移16字节得到cache

  • 输出cache没发现我们想要的数据

  • lldb打印方法buckets()

3.png

5.png

  • lldb 调用 p [p saySomething]方法
  • 在一次打印cache 里面发现了 _maybeMask value = 7 _occupied =3
  • _bucketsAndMaybeMask存放了buckets的首地址方便取bucket

4.png

  • lldb 调用 p [p saySomething]方法_maybeMask value = 7 _occupied =3但不清楚这些参数的作用

  • bucket_t中提供了相应获取imp 和sel的方法

  • saySomething 方法的sel,imp 存放在bucket中 buckets中有很多个bucket

总结

通过lldb调试,结合源码。cache中存的是方法,方法的selimp存在bucketbuckets中存放了很多个bucket。 cache中的bucketsAndMaybeMask 存放了buckets的首地址,方便平移去获取bucket

cache_t 脱离源码环境分析

说明:很多情况下我们下载下来的源码很多情况都是不能调试的,通过lldb调试也需要在源码环境,而且不方便,错一个步骤就要重新来过。下面这种方式是一种全新思路,方便我们快速去调试,局部抽取相应需要的结构来达到我们的需求。

抽取如图 3.png

脱离源码环境打印分析

4.png

6.png

7.png

我们会产生下面几个疑问

  • 问题:1 occupied在调用3个方法时变为1,maybeMask变为7?

  • 问题:2 say1 say2 方法不见了?

  • 问题:3 cache存储的位置怎么是乱序的呢?比如say2say1前面,sayHello3前面的位置是空的

带着这些疑问继续探讨cache_t_occupied_maybeMask是什么?。我们要缓存方法,首先就要是怎么把方法插入到buket中的。所以我们带着这个思路走进 cache_t源码中

cache_t 源码 insert方法

void insert(SEL sel, IMP imp, id receiver);

1.png

  • 操作了occupied +1 第一次进来occupied =0
  • occupied目的是为了记录类中有多少缓存方法,每进来一次加一个,也就是bucketsbucket的数量
  • oldCapacity 为了记录之前开辟的容量 第一次进来等于0 方面后面扩容清理

扩容流程

1.png

  • 第一次进入判断是否是空缓存 是的话reallocate开辟容量4
  • 每缓存一个方法 newOccupied+1直到缓存的容量大于开辟的4/3,进行两倍扩容也就是capacity=8

reallocate 方法分析

2.png

  • allocateBuckets开辟桶子内存
  • 进行capacity减一操作来控制边界
  • collect_free是否释放旧的内存,由freeOld控制

setBucketsAndMask方法分析

3.png

setBucketsAndMask主要根据不同的架构系统向_bucketsAndMaybeMask 和 _maybeMask写入数据

缓存方法分析

5.jpg

  • 首先拿到bucket()指向开辟这块内存首地址,也就是第一个bucket的地址,bucket()既不是数组也不是链表,只是一块连续的内存

  • hash函数根据缓存selmask,计算出hash下标。为什么需要mask呢?mask的实际作用是告诉系统你只能存前capacity - 1中的位置,比如capacity = 4时,缓存的方法只能存前面3个空位

  • 开始缓存,当前的位置没有数据,就缓存该方法。如果该位置有方法且和你的方法一样的,说明该方法缓存过了,直接return。如果存在hash冲突,下标一样,sel不一样,此时会进行再次hash,冲突解决继续缓存

cache_hash 和 cache_next

static** inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
    return (mask_t)(value & mask);
}
__arm64__

static inline mask_t cache_next(mask_t i, mask_t mask) {

    return i ? i-1 : mask;

}

cache_hash主要是生成hash下标,cache_next主要是解决hash冲突

insert调用流程

insert的我们前面内容可以看出来它的作用是缓存方法的入口,那是什么样的条件会调期insert的方法了,首先在insert方法中打个断点。运行源码看下。

1.png

  • 上图的堆栈信息显示insert的流程_objc_msgSend_uncached ->lookUpImpOrForward -> log_and_fill_cache -> cache_t::insert

在来看一下汇编流程

2.png

3.png

调用insert方法流程:[p saySomething]底层实现 objc_msgSend --> _objc_msgSend_uncached --> lookUpImpOrForward --> log_and_fill_cache --> cache_t::insert

insert流程图

4.png

方法调用缓存流程图

10.png

总结

cache_t 中各个变量的含义

  • _bucketsAndMaybeMask存储bucketsmsak(真机),macOS或者模拟器存储buckets是buckets的首地址,方便找bucket
  • _maybeMask是指掩码数据,用于在哈希算法或者哈希冲突算法中哈希下标 _maybeMask = capacity -1
  • _occupied会随着缓存的个数增加,扩容是_occupied = 0
  • 数据丢失是因为扩容的时候旧的内存回收了数据全部清除
  • cache存储bucket的位置乱序,因为位置是hash根据你的selmask生成所以不固定