本文由快学吧个人写作,以任何形式转载请表明原文出处
一、资料准备
objc4-818.2 : github.com/LGCooci/KCO…
对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。
二、cache的结构
- 进源码找到类的结构那里
- 类型是cache_t,所以进cache_t的源码 :
(1). 发现cache_t是一个结构体,有私有和公开两部分代码。
(2). 有很多的宏定义判断。
判断很多,说明对应不同的环境会用不同的代码,不具备统一普遍性,那么先看没有宏定义判断的,整理一些没有宏定义,具有普遍性的源码出来。
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
...
...
bool isConstantEmptyCache() const;
bool canBeFreed() const;
mask_t mask() const;
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
void collect_free(bucket_t *oldBuckets, mask_t oldCapacity);
static bucket_t *emptyBuckets();
static bucket_t *allocateBuckets(mask_t newCapacity);
static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));
public:
unsigned capacity() const;
struct bucket_t *buckets() const;
Class cls() const;
mask_t occupied() const;
void initializeToEmpty();
void insert(SEL sel, IMP imp, id receiver);
void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
void destroy();
void eraseNolock(const char *func);
static void init();
static void collectNolock(bool collectALot);
static size_t bytesForCapacity(uint32_t cap);
- 简单的说,cache具备普遍性的源码就如上面所示。
三、cache缓存的是什么
从上面的源码中可以看到,cache有一个insert()
函数。也就是插入函数,插入的内容应该就是存储的内容。
cahche_t的insert插入了 : SEL,IMP,和一个接收者。由此可以判断,cache缓存的是类的方法。
四、cache是如何缓存类的方法的
进入insert()
函数的实现,整理出对我们来说更必要的代码
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
//缓存写入的时候要加锁,防止出现同时操作同一对象。
runtimeLock.assertLocked();
//在类完成初始化之前,是绝不会进行缓存的
if (slowpath(!cls()->isInitialized())) {
return;
}
//常量优化缓存不会进行
if (isConstantOptimizedCache()) {
_objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
cls()->nameForLogging());
}
...
...
// 按照原样进行缓存,直到超过了预期的填充率
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// 缓存是只读的,替换掉
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// 缓存不足3/4或者7/8,按照原样使用
}
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// 搜索第一个未使用的插槽,并插入到这个插槽。
// 保证会有一个空位
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
if (b[i].sel() == sel) {
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
1. 谁在存储类的方法?
- 从上述源码可以看出在
do...while
循环中,变量b是进行了set操作的,也就是说,缓存实际是在这里进行的。对于变量b :
(1). 是bucket_t
类型的指针。
(2).并且是通过一个buckets()
函数获取的
- 看
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.
public:
static inline size_t offsetOfSel() { return offsetof(bucket_t, _sel); }
inline SEL sel() const { return _sel.load(memory_order_relaxed); }
inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {...}
void set(bucket_t *base, SEL newSel, IMP newImp, Class cls);
}
- 从
bucket_t
源码可以发现,bucket_t
存储的就是类的IMP和SEL,并且在注释中得到信息 :
(1).对于arm64e和arm64(也就是真机调试),bucket_t
存储类的方法的方式是IMP在前
(2).对于x86,i386,armv7(模拟器和其他设备),bucke_t
存储类的方法的方式是SEL在前
(3). sel()函数获取方法的名称
(4). imp()函数获取方法的实现
结论 : bucket_t在存储类的方法。
2. 如何缓存的类的方法
看完bucket_t的源码,知道了insert()中的b变量就是用来存储类的方法和名称的变量。那么如何存储的就要看do...while
。
3. 有关缓存的容量
缓存既然要存储类的方法和imp,那么肯定是要有内存的,那么缓存的容量是怎么来的?源码也是在insert()
函数中,在do...while
的上面。
-
occupied
是指占用的空间。capacity
是指容量。 -
第一个if就是说如果cache还没有使用,没有空间的时候,需要初始化缓存的大小。
先给容量赋值 : capacity = INIT_CACHE_SIZE;
然后申请内存空间 : reallocate()
,进入这个函数 :
- 第二个if中已经写了备注了缓存的空间占用没有到全部空间的3/4或者7/8,则可以继续使用。