目标
主要分析cache_t流程,对象的属性、方法都会被iOS的缓存机制缓存下来,下次调用会从缓存中查找,缓存的功能流程主要是从一下三个环节分析:
- macOS i386
- 模拟器 : x86
- 真机 : arm64
一、cache_t的整体执行流程
![](https://img.haomeiwen.com/i4127520/eb667db10f089085.png)
cache_t
中的字段:
_buckets
:数组,是bucket_t
结构体的数组,bucket_t
是用来存放方法的SEL内存地址
和IMP
的;
_mask
的大小是数组大小 - 1,用作掩码。(因为这里维护的数组大小都是2的整数次幂,所以_mask的二进制位000011, 000111, 001111)刚好可以用作hash取余数的掩码。刚好保证相与后不超过缓存大小。
_occupied
是当前已缓存的方法数。即数组中已使用了多少位置。
_maskAndBucket
是mask
和bucket
的结合体,提升了性能和效率;
源码
struct cache_t {
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
public: //对外公开可以调用的方法
static bucket_t *emptyBuckets();
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
......
}
struct bucket_t {
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
public:
inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }
inline IMP imp(Class cls) const {
uintptr_t imp = _imp.load(memory_order::memory_order_relaxed);
if (!imp) return nil;
}
cache
属性的获取,需要通过pclass的首地址平移16字节
,即首地址+0x10获取cache的地址
![](https://img.haomeiwen.com/i4127520/7b83801d2d1da440.png)
- 从源码的分析中,我们知道
sel-imp
是在cache_t
的_buckets
属性中(目前处于macOS环境),而在cache_t
结构体中提供了获取_buckets
属性的方法buckets()
; - 获取了
_buckets
属性,就可以获取sel-imp
了,这两个的获取在bucket_t
结构体中同样提供了相应的获取方法sel()
以及imp(pClass)
.
cache的缓存原理及内存计算方式
缓存过程主要步骤:
1 、计算当前bucket
占用量;
2 、根据bucket
在buckets
中的占用比,开辟空间
3、根据cache_hash
方法,计算sel-imp
存储的哈希下标,存入sel, imp, cls
![](https://img.haomeiwen.com/i4127520/e8e7af0e4afd729b.png)
ALWAYS_INLINE
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());
/*
step1 创建临时变量 newOccupied ,oldCapacity
*/
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
/*
step2 进行buckets的计算
*/
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
//如果小于等于占用内存的3 /4 则什么都不用做。
}
else {
//扩容原因:第一次申请开辟的内存容量是 4 ,如果已经有3个bucket插入到cache里面,再次插入一个就会存满这个容量,为了保证读取的正确性,就对其进行扩容
// 扩容算法:有capacity时扩容为两倍,没有就初始化为INIT_CACHE_SIZE 也就是4
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
// 内存 库容完毕
}
/*
step3 计算好容量之后,进行插入sel imp class
*/
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m); //求cache的hash ,通过计算得到sel存储的下标
mask_t i = begin;
//遍历操作
do {
// 如果sel 不存在就将sel存进去
if (fastpath(b[i].sel() == 0)) {
incrementOccupied(); //Occupied ++
b[i].set<Atomic, Encoded>(sel, imp, cls); //存入 sel imp cls
return;
}
// 如果sel 存在就返回
if (b[i].sel() == sel) {
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
cache_t::bad_cache(receiver, (SEL)sel, cls);
}
补充
cache | 缓存 |
---|---|
bucket | 桶,一桶的量 |
mask | 面具;subnet mask ,子网掩码 |
occupied | 已占用的;使用中的 |
capacity | 容量; 能力 |
shift | 转移, 移位 |
reallocate | 重新分配 |
increment | 增量 |
内存计算主要是根据cache_hash
方法,即哈希算法
,计算sel-imp
存储的哈希下标,分为以下三种情况:
如果哈希下标的位置未存储sel
,即该下标位置获取sel等于0,此时将sel-imp
存储进去,并将occupied
占用大小加1
如果当前哈希下标存储的sel
等于 即将插入的sel
,则直接返回
如果当前哈希下标存储的sel
不等于 即将插入的sel
,则重新经过cache_next
方法 即哈希冲突算法
,重新进行哈希计算,得到新的下标,再去对比进行存储
cache_hash
:哈希算法
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
return (mask_t)(uintptr_t)sel & mask; // 通过sel & mask(mask = cap -1)
}
cache_next
:哈希冲突算法
#if __arm__ || __x86_64__ || __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask; //(将当前的哈希下标 +1) & mask,重新进行哈希计算,得到一个新的下标
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask; //如果i是空,则为mask,mask = cap -1,如果不为空,则 i-1,向前插入sel-imp
}
总结
什么时候触发cache缓存机制
objc_msgSend第一次发送消息会触发方法查找,找到方法后会调用cache_fill()
方法把方法缓存到cache
中,这个在后面分析方法的本质的时候会提到。
网友评论