oc类底层cache_t详解

367 阅读2分钟

0a000581fca74098bc53b876f5e957ae.jpeg 这篇文章是 iOS类的底层探索(上)-类的结构cache_t的补充。

一. cache_t 的结构

1、cache_t结构属性

struct cache_t { // 8+8 = 16

private:

    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;//8

    union {

        struct {

            explicit_atomic<mask_t>    _maybeMask;//4

#if __LP64__      //long point  64位

            uint16_t                   _flags; //2

#endif

            uint16_t                   _occupied; //2

        };

        explicit_atomic<preopt_cache_t *> _originalPreoptCache;//8

    };
    .....
    unsigned capacity() const;

    struct bucket_t *buckets() const;
    .............
    
    void insert(SEL sel, IMP imp, id receiver);//添加缓存方法
    .............
 }

我们看到了void insert(SEL sel, IMP imp, id receiver);这个insert方法里面有SELIMPreceiver这些参数。看出来这事一个添加方法到cache_t的函数。我们进到里面看看具体做了什么。

void cache_t::insert(SEL sel, IMP imp, id receiver)
{
runtimeLock.assertLocked();


    // Never cache before +initialize is done

    if (slowpath(!cls()->isInitialized())) {

        return;

    }

    if (isConstantOptimizedCache()) {

        _objc_fatal("cache_t::insert() called with a preoptimized cache for %s",

                    cls()->nameForLogging());

    }
    
    ................
    //方法缓存

    bucket_t *b = buckets();

    mask_t m = capacity - 1;//最大不超 capacity - 1

    mask_t begin = cache_hash(sel, m);//起始

    mask_t i = begin;



    // Scan for the first unused slot and insert there.

    // There is guaranteed to be an empty slot.

    do {

        if (fastpath(b[i].sel() == 0)) {//没值

            incrementOccupied();

            b[i].set<Atomic, Encoded>(b, sel, imp, cls());//set 一个sel

            return;

        }

        if (b[i].sel() == sel) {

            // The entry was added to the cache by some other thread

            // before we grabbed the cacheUpdateLock.

            return;

        }

    } while (fastpath((i = cache_next(i, m)) != begin));



    bad_cache(receiver, (SEL)sel);
    ................
 }

这段代码里,我们发现b.set<Atomic, Encoded>(b, sel, imp, cls());方法把把sel,imp,cls等添加到了b=bucket_t的结构体里,还有buckets()capacitycache_hash(sel, m)他们又做了什么? 让我们来逐一分析他们。

2、验证bucket_t 用来存储sel, imp

我们用objc4-838.1 进行分析cache_t:

image.png

image.png

image.png

3、当调用method1后bucket_t 储蓄了什么

image.png

image.png

我们发现刚运行的method1 在bucket_t 中找到,并且respondsToSelector:后面在的都是(null)。

二. bucket_t 的扩容解析

1、扩容规则

image.png 从代码上看,第一次空了会进行初始化capacity的长度INIT_CACHE_SIZE.

image.png

#if __arm__  ||  __x86_64__  ||  __i386__



// objc_msgSend has few registers available.

// Cache scan increments and wraps at special end-marking bucket.

#define CACHE_END_MARKER 1
#elif __arm64__ && !__LP64__



// objc_msgSend has lots of registers available.

// Cache scan decrements. No end marker needed.

#define CACHE_END_MARKER 0

从上面代码,我们可以判断CACHE_END_MARKER == arm7(非arm64)|x86_64|i386 为1 ,arm64 为 0 INIT_CACHE_SIZE == arm7(非arm64)|x86_64|i386 为4 ,arm64 为 2

void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)

{

    bucket_t *oldBuckets = buckets();//获取老的bucket

    bucket_t *newBuckets = allocateBuckets(newCapacity);//新开辟的新bucket



    // Cache's old contents are not propagated. 

    // This is thought to save cache memory at the cost of extra cache fills.

    // fixme re-measure this



    ASSERT(newCapacity > 0);

    ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);



    setBucketsAndMask(newBuckets, newCapacity - 1);

    

    if (freeOld) {//判断是否需要释放老的bucket

        collect_free(oldBuckets, oldCapacity);

    }

}

image.png

2、真机验证

image.png image.png

image.png 因为我用的是iphone8p arm7的框架和x86_64一样。使用了大小等于桶子长度的3/4的时候,进行2倍扩容。

3、一个类的结构cache_t关系图

image.png

总结:cache扩容规则有

(1)在arm7(非arm64)|x86_64|i386框架下--当缓存的大小等于桶子长度的3/4的时候,进行2倍扩容

(2)arm64当缓存的大小等于桶子长度7/8的时候,进行2倍扩容,当桶子的长度小于等于8的时候,不会扩容。