cache_t 源码
在OC底层探索(五) 类的结构分析文章中,我们分析objc_class源码时,有提到过其属性 cache_t cache属性的大小为16字节。
那么我们今天就着重分析以下cache_t到底是干什么的。
准备工作
- 在LGPerson类中申请两个方法:
@interface LGPerson : NSObject
-(void)sayHello;
-(void)sayCode;
- 在main.h中调用这两个方法,并打上断点
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
Class pClass = [LGPerson class];
[p sayHello];
[p sayCode];
NSLog(@"%@",p);//断点
}
return 0;
}
打印
- 打印LGPerson首地址
- 在objc_class中,cache属性在第三位,前两位分别时8位的isa和8位的superclass,所以首地址加一获取到cache_t。
打印结果如下:
- 在cache_t源码中,调用struct bucket_t *buckets();方法获取bucket_t类型数据。
- 在bucket_t数据中分别由sel() 和 imp(Class cls)方法获取方法。
- 使用$13.sel() 获取到了sayHello方法,那么sayCode方法在哪呢?
打印结果如下:
- 由于_buckets属性是一个数组,那么我们可以使用指针平移来获取下一个指针的值。
- 打印*($12 + 1)就获取到sayCode方法。
源码分析
cache_t 源码
struct cache_t {
#if 1 // Mac
struct bucket_t * _buckets;
mask_t _mask;
#elif 1 // 真机
uintptr_t _maskAndBuckets;
mask_t _mask_unused;
// How much the mask is shifted by.
static constexpr uintptr_t maskShift = 48;
// Additional bits after the mask which must be zero. msgSend
// takes advantage of these additional bits to construct the value
// `mask << 4` from `_maskAndBuckets` in a single instruction.
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
#endif
uint16_t _flags; // 标志位
uint16_t _occupied; // 被占用的
public:
static bucket_t *emptyBuckets();
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
unsigned capacity();
bool isConstantEmptyCache();
bool canBeFreed();
void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
void insert(Class cls, SEL sel, IMP imp, id receiver);
};
}
分析
- 在真机状态下,缓存信息存储在_maskAndBuckets中
uintptr_t _maskAndBuckets;
mask_t _mask_unused;
- 在MAC状态下,缓存信息存储在_buckets中。
struct bucket_t * _buckets;
mask_t _mask;
- 根据buckets()方法读取缓存信息
struct bucket_t *buckets();
- 根据insert()方法向缓存中添加数据
void insert(Class cls, SEL sel, IMP imp, id receiver);
- 根据occupied()方法获取方法的总个数
mask_t occupied();
cache_t::insert 源码
ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
// 1、 计算相关的occupied
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
// 2、 判断如果是创建 进行初始化
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
//3 判断是否需要扩容
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // 4 3 + 1 bucket cache_t
// Cache is less than 3/4 full. Use it as-is.
}
//4 扩容操作;
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 扩容两倍 4
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;
//5;进行相关的方法存储
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(sel, imp, cls);
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));
cache_t::bad_cache(receiver, (SEL)sel, cls);
}
分析
1. 计算相关的occupied
- occupied()默认值为0;
- 每次调用插入缓存方法时都会加一;
- 所以在cache_t源码中occupied是缓存中方法的总个数。
2. 判断如果是创建 进行初始化
-
判断当前cache是否为空,如果为空,就申请4字节内存,并初始化。
-
其中INIT_CACHE_SIZE 为申请内存空间的大小,
0001 => 左移2位 => 0100
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2),
- reallocate 初始化Cache
void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
#ifdef __arm__
mega_barrier();
_buckets.store(newBuckets, memory_order::memory_order_relaxed);
mega_barrier();
_mask.store(newMask, memory_order::memory_order_relaxed);
_occupied = 0;
#elif __x86_64__ || i386
_buckets.store(newBuckets, memory_order::memory_order_release);
_mask.store(newMask, memory_order::memory_order_release);
_occupied = 0;
#else
3. 判断是否需要扩容:如果新的值小于或等于原来的3/4,不做任何处理;大于3/4就扩容2倍
fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)
- 其中newOccupied是当前需要插入方法的个数
- CACHE_END_MARKER 是固定值
- capacity 是申请的总内存空间的大小
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 扩容两倍 4
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
- 当新的值大于3/4时,以当前内存大小为基础扩容两倍;
- 并重新初始化,之前存储的方法全部都清空,因为扩容是将重新开辟一块内存空间。
4. hash方式存储方法
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
return (mask_t)(uintptr_t)sel & mask;
}
- 在cache存储中OC使用的是hash方式存储
- 其中mask为 mask = capacity - 1; 总的内存大小减一。