我们在上个章节类的结构分析中大概描述了一下类的属性,成员变量,实例方法,类方法的存储位置。接下来我们去分析类的结构体cache_t cache做了些什么
附上类的结构体信息
struct objc_class : objc_object {
// Class ISA; //8
Class superclass; //8
cache_t cache; //16 // formerly cache pointer and vtable
class_data_bits_t bits;
...省略部分信息...
};
研究cache_t结构体
struct cache_t {
struct bucket_t *_buckets; //8
mask_t _mask; //4 typedef uint32_t mask_t;
mask_t _occupied; //4
public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
mask_t capacity();
bool isConstantEmptyCache();
bool canBeFreed();
static size_t bytesForCapacity(uint32_t cap);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
struct bucket_t * find(SEL sel, id receiver);
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
结构体里面的属性一一分析
- struct bucket_t *_buckets 一个结构体指针,指向结构体bucket_t,里面只有两个私有的属性_imp,_sel.
struct bucket_t {
private:
#if __arm64__
uintptr_t _imp; //typedef unsigned long uintptr_t;
SEL _sel;
#else
SEL _sel;
uintptr_t _imp;
#endif
...省略部分信息...
};
- _mask 一个掩饰的值,到后面的分析可以得到是 hash链表的长度-1。
- ** _occupied** 已占用空间。
OC方法在cache_t的cache流程
我们通过bucket_t这个结构体,我们可以很清楚的看到它存的就是 imp和sel,也就是我们的方法。
我们在objc4的源码中 在文件objc_cache.mm的开始位置可以看到有这么一个注释说明
* Cache readers (PC-checked by collecting_in_critical())
* objc_msgSend*
* cache_getImp
*
* Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked)
* cache_fill (acquires lock)
* cache_expand (only called from cache_fill)
* cache_create (only called from cache_expand)
* bcopy (only called from instrumented cache_expand)
* flush_caches (acquires lock)
* cache_flush (only called from cache_fill and flush_caches)
* cache_collect_free (only called from cache_expand and cache_flush)
我们先忽略objc_msgSend这个流程,我们可以直观的看到Cache writes的流程,从cache_fill的故事开始
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}
cache_fill的函数里面调用了cache_fill_nolock函数
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
// Cache is too full. Expand it.
cache->expand();
}
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
bucket_t *bucket = cache->find(sel, receiver);
if (bucket->sel() == 0) cache->incrementOccupied();
bucket->set<Atomic>(sel, imp);
}
接下来开始我们的探索之路
- 做一个简单的判断,类是否isInitialized,是否可以cache_getImp
if (!cls->isInitialized()) return;
if (cache_getImp(cls, sel)) return;
- 从类里面拿到cache
cache_t *cache = getCache(cls);
cache_t *getCache(Class cls)
{
assert(cls);
return &cls->cache;
}
- 拿到新的占用空间occupied,通过以后的occupied的值+1
mask_t newOccupied = cache->occupied() + 1;
//occupied() return _occupied;
- 拿到cache的容量,是通过_mask值来的,如果初始值为0取0,否在在原有的基础上_mask+1
mask_t capacity = cache->capacity();
mask_t cache_t::capacity()
{
return mask() ? mask()+1 : 0;
//mask() return _mask;
}
- 这是cache重要的一步
- cache是空的 cache->reallocate
- 第3步 新的占用空间 <= 容量 的 3/4 就不做处理
- 如果 新的占用空间 <= 容量 的 3/4 cache->expand()表示占用空间太满了,需要扩容
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
//INIT_CACHE_SIZE 4
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
// Cache is too full. Expand it.
cache->expand();
}
- 从cache的cache_hash哈希表中通过sel去查找bucket,然后在给bucket设置sel ,imp。
bucket_t *bucket = cache->find(sel, receiver);
if (bucket->sel() == 0) cache->incrementOccupied();
bucket->set<Atomic>(sel, imp);
通过上面的6步流程,我们基本上了解到了cache的write过程了,那么接下来我们对第5步做详细的描述。
- cache->isConstantEmptyCache() 顾名思义 判断是一个不变的空的cache,那我们看看它是如何的。
这里为了更加方便的查看,我会把一些定义的值或者返回的源码直接拿过来放在了这里面。而且注释说明我也会直接写在里面了
bool cache_t::isConstantEmptyCache()
{
return
occupied() == 0 &&
buckets() == emptyBucketsForCapacity(capacity(), false);
//occupied() return 0;
//buckets() return _buckets; _buckets是一个指针还没有初始化 也为0
}
bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true)
{
cacheUpdateLock.assertLocked();
// return sizeof(bucket_t) * (cap + 1);
size_t bytes = cache_t::bytesForCapacity(capacity);
*
第一次进来的时候是没有cache的,则capacity = 0,bucket_t结构体的俩个对象都是8字节共16字节 ,则实际上bytes的值就为16 。
*
// Use _objc_empty_cache if the buckets is small enough.
#if DEBUG
// Use a smaller size to exercise heap-allocated empty caches.
# define EMPTY_BYTES ((8+1)*16)
#else
# define EMPTY_BYTES ((1024+1)*16)
#endif
//OBJC_EXPORT struct objc_cache _objc_empty_cache
if (bytes <= EMPTY_BYTES) {
return (bucket_t *)&_objc_empty_cache;
}
*
走到这个就会直接返回了
bucket_t * 8 1000
_objc_empty_cache 16 10000
& 00000
*
//下面的代码与我们的流程没有关系 暂不分析
// Use shared empty buckets allocated on the heap.
static bucket_t **emptyBucketsList = nil;
static mask_t emptyBucketsListCount = 0;
mask_t index = log2u(capacity);
if (index >= emptyBucketsListCount) {
if (!allocate) return nil;
mask_t newListCount = index + 1;
bucket_t *newBuckets = (bucket_t *)calloc(bytes, 1);
emptyBucketsList = (bucket_t**)
realloc(emptyBucketsList, newListCount * sizeof(bucket_t *));
// Share newBuckets for every un-allocated size smaller than index.
// The array is therefore always fully populated.
for (mask_t i = emptyBucketsListCount; i < newListCount; i++) {
emptyBucketsList[i] = newBuckets;
}
emptyBucketsListCount = newListCount;
if (PrintCaches) {
_objc_inform("CACHES: new empty buckets at %p (capacity %zu)",
newBuckets, (size_t)capacity);
}
}
return emptyBucketsList[index];
}
这个步骤总结一下:
- 占用空间occupied是否为空,即_occupied值是否为0;
- _buckets这个指针是否未初始化,
bytes 函数的返回值sizeof(bucket_t)&(0 + 1); 然后再和一个EMPTY_BYTES做比较。
- cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
//return !isConstantEmptyCache();
bool freeOld = canBeFreed();
*
从上面的流程中可以得到isConstantEmptyCache返回的是0 !0就是true了
*
//return _buckets; 一个获取buckets的指针的函数
bucket_t *oldBuckets = buckets();
//根据容量 系统开辟一个新的buckets
bucket_t *newBuckets = allocateBuckets(newCapacity);
// 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);
*
做了下面几个事 我们可以发现 mask的值为 newCapacity - 1 为3
_buckets = newBuckets; //_buckets开辟了内存空间
_mask = newMask; //_mask赋值为 4 - 1 = 3
_occupied = 0;
*
//下面就是清楚旧的buckets 就是把缓存清理干净
if (freeOld) {
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false);
}
}
- 初始化相对于的值_buckets,_mask,_occupied.
- 清理缓存
- cache->expand(); cache扩容
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
*
能进入到扩容的这里面 _mask 是有值的,并且是并且我们知道得到的oldCapacity是_maks + 1,
申请的一份新的容量是 oldCapacity * 2,我们可以验证一下开辟两倍的空间是最划算的。
*
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
//这里如同刚开始没有缓存的时候,重新让系统来开辟
reallocate(oldCapacity, newCapacity);
}
通过把第5步给拎出来,单独探索,我们基本上了解了cache的容量的大小的处理,以及新开辟的内存空间的大小。
接下来我们继续探索第6步。
- bucket_t *bucket = cache->find(sel, receiver);
bucket_t * cache_t::find(SEL s, id receiver)
{
assert(s != 0);
bucket_t *b = buckets(); //获取_buckets
mask_t m = mask(); //获取_mask
//return (mask_t)(uintptr_t)sel & mask;
mask_t begin = cache_hash(s, m);
mask_t i = begin;
do {
if (b[i].sel() == 0 || b[i].sel() == s) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
//cache_next(i,m) return return (i+1) & mask;
*
这里通过一个hash算法,找到我们cache_t中buckets列表里面需要匹配的bucket。
*
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)s, cls);
}
- if (bucket->sel() == 0) cache->incrementOccupied(); 如果找到的_sel是空的,我们先把已占用的空间加1,一般我们第一次调用的时候,或者是缓存全部清理了之后,这个时候就拿到的_sel会是空的,目的是把我们的当前的_sel再缓存。
void cache_t::incrementOccupied()
{
_occupied++;
}
- bucket->set<Atomic>(sel, imp); 当前取到的bucket就会赋值自己的属性_imp,_sel。
void bucket_t::set(SEL newSel, IMP newImp)
{
_imp = (uintptr_t)newImp;
if (_sel != newSel) {
if (atomicity == Atomic) {
mega_barrier();
}
_sel = newSel;
}
}
至此为止cache_t方法缓存的流程就分析完毕了。