iOS-OC 方法缓存机制(cache_t)

一,首先让我们看看类的结构
image.png

除了具体的类信息,还有一个重要的结构-->方法缓存cache,本文就是说明它的运行机制.

方法缓存的结构如下
image.png

1> 方法缓存里有三个属性:
1.1 bucket_t维护着一个散列表,里面的元素是以方法名"SEL"为key,方法的实现"IMP"为value的字典,
1.2 _mask 它代表的是散列表的长度-1,最初分配的值是4, 它的作用是 传入的方法名SEL & _mask得到的这个值,就是方法保存的位置,
1.3 _occupied已经缓存的方法数量,它的数量<= 3/4 * _mask,否则_mask会扩容为原来的两倍,直到<= (1<<16)

下面来看看它里面的判断逻辑是怎么样的
主要代码在这个位置

image.png

void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
    mask_t newOccupied = occupied() + 1;  插入新的方法缓存,数量+1
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {如果还没有缓存过方法
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE; 这是一个容量为4的宏,最初分配的容量就是4
        reallocate(oldCapacity, capacity, /* freeOld */false);创建-分配内存
    }
    else if (fastpath(newOccupied <= capacity / 4 * 3)) {
        保证缓存表存的方法数小于等于 容量的3/4 
    }
    else {超过容量的3/4,容量翻倍
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {不能超过最大值,最大值是1<<16
            capacity = MAX_CACHE_SIZE;
        }
        超过容量后,将之前缓存的方法全部清空 "true"
        reallocate(oldCapacity, capacity, true);
    }

    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m); 
    mask_t i = begin; 通过 sel & mask 计算出sel该存放的位置 "i"
    如果计算出来的值没有缓存方法,则直接插入,保存;
    如果已经有方法插入了,在__arm64__架构 i--(其他架构i++),即,如果被占用,往上走一格,还被占用继续往上走,
因为规则限定了3/4,所以肯定能找到没有保存方法的位置.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set(sel, imp, cls);  保存方法
            return;
        }
        if (b[i].sel() == sel) {
        这种情况是其他线程,已经把方法添加到这里了,那就直接退出循环
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin)); "i"不等于初始位置 i--,继续循环.

说明:
1> 方法的存储并不是按照数组一样从上到下存储,而是通过 sel & mask来存储的,所以难免会存在内存利用率低,但是加快了方法查找的速度,即:空间换时间.

2> 方法缓存是先于isa的方法查找,就是说,缓存中找不到,再到自己的方法列表中查找,找到之后也会缓存到cache_t中,如果是父类的方法,也是会缓存到自己的表当中的.

3> arm64 之后增加了很多 &mask的操作,获取具体的类信息,也是通过 bits & mask 来获取,里面存储的信息更多了(文中提到的mask 不同的地方,mask的值是不同的).

4> 如果cache_next(i,m)循环到0,还未找到,赋值i == mask,继续循环,直到i == begin,证明没有缓存这个方法,这是最差的情况,相当于遍历了一遍数组.

你可能感兴趣的:(iOS-OC 方法缓存机制(cache_t))