众所周知oc的类的底层是一个结构体,如下。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
.......
}
该结构体包含了4个成员变量:isa、superclass、cache、bits。其中isa是指向元类的、superclass是指向父类的、bits是通过bits.data()函数来获取rw,从而能够得到类的其他信息,比如ivar、property、method、protocol等信息。
那么cache是用来做啥的呢? ——使用来缓存对象调用过的方法的,下次调用方法时就可以直接在这里找到了。
首先来看一下cache这个结构体的内容是啥:
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
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(cache_key_t key, id receiver);
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
// bucket_t的结构体内容
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;
MethodCacheIMP _imp;
#endif
public:
inline cache_key_t key() const { return _key; }
inline IMP imp() const { return (IMP)_imp; }
inline void setKey(cache_key_t newKey) { _key = newKey; }
inline void setImp(IMP newImp) { _imp = newImp; }
void set(cache_key_t newKey, IMP newImp);
};
cache结构体的成员变量有3个:_buckets、 _mask、_occupied
通过源码可以很明显的看出来,_buckets是用来缓存调用过的方法的。
那么cache是怎么缓存方法的呢,有啥缓存策略么?
通过看cache结构体中的expend函数,我们可以窥探到cache一些扩容的策略
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
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);
}
INIT_CACHE_SIZE:4
int32_t newCapacity = oldCapacity ? oldCapacity * 2 : INIT_CACHE_SIZE
通过这2句代码看出cache每次扩容都是在原来的基础上扩容2倍,或者第一次就初始化为容量4。注: 在扩容的时候,会把之前缓存的全部清零。
那么啥情况下回触发扩容呢?
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);
cache_key_t key = getKey(sel);
// 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(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
通过这个cache_fill_nolock函数我们可以看出,当开始要缓存一个方法时,
- 首先会先判断cache中有没有存在这个SEL了,如果能找到,就直接return(if (cache_getImp(cls, sel)) return;)
- 如果没有,则先判断当前的cache的容量。(newOccupied <= capacity / 4 * 3)通过这句代码可以看出如果超过3/4,就会触发expend函数,就是扩容。
- 最后无论是否扩容了,都会把这个最新调用的SEL存到cache里。
bucket_t * bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
最后总结cache的调用流程入下图:
最后的流程图是盗用了网络上某大神的图片,非本人自画,感谢!
网友评论