前言:
上一篇探索了属性 成员变量 方法
在类
中是如何存储的,即存储在class_ro_t *ro
中,上一篇中提到为什么在rw
中也能打印相应的属性 方法
呢?
因为rw
中的属性 方法
在编译期是没有的,是在运行时从ro
中copy
赋值到rw
中。
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 不是8 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
···
}
从类
的源码中不难看出前两个成员
分别是isa
和superclass
,上一篇我们探索了属性 成员变量 方法
在bits
中的存储,那么cache_t cache
中存储的是什么呢 ?
cache_t cache
源码:
struct cache_t {
struct bucket_t *_buckets; // 8
mask_t _mask; // 4
mask_t _occupied; // 4
public:
struct bucket_t *buckets();
···
}
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_t cache
中存储的是方法
的缓存。
1. cache_t cache LLDB
简单分析
首先创建一个类
,代码如下:
@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
- (void)sayCode;
- (void)sayMaster;
- (void)sayNB;
+ (void)sayHappy;
@end
#import "LGPerson.h"
@implementation LGPerson
- (void)sayHello{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)sayCode{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)sayMaster{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)sayNB{
NSLog(@"LGPerson say : %s",__func__);
}
+ (void)sayHappy{
NSLog(@"LGPerson say : %s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = [LGPerson class];
// cache_t 为什么没有 - 第一次
[person sayHello];
[person sayCode];
[person sayNB];
}
}
通过打印cache_t cache
,发现方法缓存确实存在cache_t cache
中(有时候系统可能会出现问题,打印出cache_t cache
中的_key
和_imp
为空,多运行打印几次就ok)。
2.cache_t cache
流程源码分析
通过上面的打印查看了cache_t cache
的缓存内容,接下来查看源码分析一下cache_t cache
的具体流程:
首先查看cache_t
中容量mask_t capacity()
的实现:
mask_t capacity()
的实现:
mask_t cache_t::capacity()
{
return mask() ? mask()+1 : 0;
}
mask_t cache_t::mask()
{
return _mask;
}
在实现中对cache_t
的_mask
进行+1
,那么什么是调用对这个capacity()
的呢?
通过搜索源码查看在expand()
方法中调用:
void cache_t::expand() // 扩容
{
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);
}
而扩容 expand()
方法是在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);
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_fill_nolock
方法中:
1.先从 cache 中获取 imp ,获取到直接 return
if (cache_getImp(cls, sel)) return;
2.获取 cache 和 key,第一次调用 sayHello 方法,cache 中_mask 和 _occupied 为0
3. 对 cache 的 occupied + 1, 并获取容量 capacity (此时为0),
4. 判断是否是 isConstantEmptyCache ,如果是 EmptyCache,
调用cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE)
跳转到第 7 步;
INIT_CACHE_SIZE 为:1 << 2 = 4
enum {
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2)
}
5. 判断桶子 Buckets 的占用量是否是 小于等于 容量的3/4
即:newOccupied <= capacity / 4 * 3,小于3/4 直接跳转到 第8步;
6. 当桶子 Buckets 的占用量达到临界点时,执行扩容 cache->expand();
expand() 方法中:
6.1 获取 oldCapacity = capacity() // 4;
newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE; // 8
6.2 reallocate(oldCapacity, newCapacity)
即:reallocate(4, 8),跳转到 第7步;
7. reallocate(mask_t oldCapacity, mask_t newCapacity) 传入一个oldCapacity = 0,
newCapacity = 1 << 2 = 4
7.1 先获取 freeOld 标识,是否释放旧缓存,bool freeOld = canBeFreed()
即:bool cache_t::canBeFreed()
{
return !isConstantEmptyCache();
}
此时,没有缓存 cache, 所以 freeOld 为false
7.2 创建一个新的 bucket_t *newBuckets,开辟4个位置
7.3 用新容量 newCapacity - 1,对创建的 newBuckets 进行设置
setBucketsAndMask(newBuckets, newCapacity - 1);
7.4 判断 释放标识 freeOld,
true: 释放旧的 oldBuckets 和 oldCapacity
即:cache_collect_free(oldBuckets, oldCapacity);
false:不执行
此时 freeOld 为 false
8. 从 cache 中根据 key 查找 合适 Buckets
8.1 获取 buckets,获取 mask = 3 (sort)
bucket_t *b = buckets();
mask_t m = mask();
8.2 通过对 k (对 sel 哈希的到的 cache_key_t key = getKey(sel)) 和 m 进行哈希得到下标
mask_t begin = cache_hash(k, m);
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
通过 sel 和 mask 的位运算,计算出一个合理的 begin,就是哈希的下标
8.3 通过 begin, do...while 循环,查找 bucket_t。
查找到就返回,查找不到返回 bad_cache
9. 桶子 bucket 查找到以后,可以占用,对 _occupied++。
10. 把 key 和 imp 保存在桶子里 bucket->set(key, imp)。
到此,cache_t cache中的缓存存储流程分析完成。
简单的总结,
1. 先查找缓存,缓存命中直接返回,
2. 当没有缓存时,开辟新的缓存并初始化,然后查找桶子 _buckets,然后 _occupied ++,然后存储 set(key, imp);
3. 当有缓存,并小于容量的3/4时,直接查找桶子 _buckets,然后 _occupied ++,然后存储 set(key, imp);
4. 当有缓存,大于容量的3/4时,扩容到二倍,查找桶子 _buckets,然后 _occupied ++,然后存储 set(key, imp);