-
我之前的博客介绍了
objc_class中的bits类的结构探索,当时我们为了探究结构,略掉了cache_t,今天我们就来分析一波这是个什么东东~ -
首先给大家介绍2个东西(我知道大家都知道。不过,还是要说一下,万一有不知道的呢。例如:我😆)
- SEL:简单来说就是方法编号
- IMP : 一个指向方法实现的指针(这两个我就不解释了,如果真不知道的话,百度一哈就行了)
-
开始步入正题!!
1. 定位cache_t的结构
我们要先看看cache_t 是什么样子的,然后我们在一步步分析。
-
- 首先找到
objc_class:
- 首先找到
- 2.我们点进去就可以观察到
cache_t的结构了!! 源码:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<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;
// Ensure we have enough bits for the buckets pointer.
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
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();
//不继续复制了,,太多了。有条件的自己可以研究下
-
- 我们就可以看到
cache_t的结构了,不过,这么长的代码,这不是搞咱们心态吗。看看传说中的if-else~
- 我们就可以看到
宏定义:
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#if defined(__arm64__) && __LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
-
CACHE_MASK_STORAGE_HIGH_16: 真机&&64位 -
CACHE_MASK_STORAGE_LOW_4: 真机&&非64位 -
CACHE_MASK_STORAGE_OUTLINED:模拟器和Mac呗
我们今天来根据Mac讲解(毕竟会一个,其他的都差不多。。)
2. cache_t结构分析
2.1 通过查看源码分析
-
我们第一眼望去,有效的东西:
buckets、mask、flags、occupied,flags看着就是标记,先略过。 -
刚想开始分析,不过代码又长、英文有不行,看这个就烦!但没有好办法!只能咬咬牙(研究底层就要试着探索,可能探索的方向不对,会做无用功,但是如果不探索,都不知道对不对!)
-
我们先看下
buckets的结构:
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
这特么不就是存储SEL和IMP么。我擦,那我迫不及待要验证一波 了,兄弟们~
2.2. 通过lldb调试分析
2.2.1 调用单个方法分析
-
- 创建一个
XGTest的类:
- 创建一个
@interface XGTest : NSObject
- (void)say1;
- (void)say2;
- (void)say3;
- (void)say4;
- (void)say5;
@end
XGTest *test = [XGTest alloc];
Class tClass = [test class];
[test say1];
通过调用方法say1,然后定位到cache的结构。
-
- 我们可以看到我们想要的东西了(
_buckets,_mask,_flags,_occupied),我们观察源码是否有可以获取的方法(还真有!):
- 我们可以看到我们想要的东西了(
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();
-
- 所以我们可以通过调用
buckets()查看结构
- 所以我们可以通过调用
发现:这个buckets里面不就是存储sel和imp吗!!
-
- 我们通过观察,可以发现
bucket_t中有获取sel和imp的方法
- 我们通过观察,可以发现
-
- 那还等什么!我要得到他!!
- 那还等什么!我要得到他!!
-
- 完美获取到了!
2.2.2 调用多个方法分析
我们调用一个方法
cache的变化已经知道了,那么多个方法呢?
-
- 增加一个方法!
XGTest *test = [XGTest alloc];
Class tClass = [test class];
[test say1];
[test say2];
-
- 我们打印下cache的结构:
- 我们打印下cache的结构:
-
- 我们发现
_occupied有变化,这个我们一会在分析,我们现在先了解buckets。
- 我们发现
兄弟们! 看我操作!!
诶?我们的say2呢?咋没了。那我不要找一找?
-
4. 那我大胆猜测一波!
buckets有s,那他是不是有多个?,会不会是个类似数组结构?那我通过指针偏移可不可以得到想要的东西?那验证一下呗! -
- OK了,我们已经了解
buckets里面存sel和imp了。
- OK了,我们已经了解
3. cache_t实现原理分析
刚才我们发现
_occupied有变化,这个东西翻译是占位。。也有点意思~
-
那我们看下,
_occupied如何变化的?而且当方法增多,mask也改变。还有buckets怎么存的? -
- 我们看
occupied的变化。。咋变的?,那只能看看有没有让他变化的方法啊!
- 我们看
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();
不辞辛苦!让我看到了
incrementOccupied这!然后我们顺藤摸瓜~看看谁调用了它!
-
- 通过全局搜索!发现只有一个地方
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
-
- 诶呀!终于摸到了敌人后方!,
insert不就是插入吗!我有预感,这个就是我们要看的东西!(但是我没有证据😆)
- 诶呀!终于摸到了敌人后方!,
-
- 那我要提供证据了啊!
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
#if CONFIG_USE_CACHE_LOCK
cacheUpdateLock.assertLocked();
#else
runtimeLock.assertLocked();
#endif
ASSERT(sel != 0 && cls->isInitialized());
//前面是断言,,没有!!分为2部分看
//第一部分
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
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.
}
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;
// 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.
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);
}
刚开始给我整几个断言!增加代码长度?以为能唬住我看下去的欲望?岂不是在逗我笑!!
-
- 为了帮助分析,我把代码整理成两部分:首先if-else判断分析:
通过上图我们可以清楚内存开辟!
-
- 那么看他第二部分代码:
- 那么看他第二部分代码:
这就是cache实现原理,当然,也有好多方法没有具体说明,例如:cache_hash(如何哈希的)
这些就交给大家了,毕竟所有都写了,就没有探索的意义了!