这篇文章是 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方法里面有SEL,IMP,receiver这些参数。看出来这事一个添加方法到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(),capacity,cache_hash(sel, m)他们又做了什么?
让我们来逐一分析他们。
2、验证bucket_t 用来存储sel, imp
我们用objc4-838.1 进行分析cache_t:
3、当调用method1后bucket_t 储蓄了什么
我们发现刚运行的method1 在bucket_t 中找到,并且respondsToSelector:后面在的都是(null)。
二. bucket_t 的扩容解析
1、扩容规则
从代码上看,第一次空了会进行初始化
capacity的长度INIT_CACHE_SIZE.
#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);
}
}
2、真机验证
因为我用的是
iphone8p arm7的框架和x86_64一样。使用了大小等于桶子长度的3/4的时候,进行2倍扩容。
3、一个类的结构cache_t关系图
总结:cache扩容规则有
(1)在arm7(非arm64)|x86_64|i386框架下--当缓存的大小等于桶子长度的3/4的时候,进行2倍扩容
(2)arm64当缓存的大小等于桶子长度7/8的时候,进行2倍扩容,当桶子的长度小于等于8的时候,不会扩容。