- 我们都知道OC中
属性
是存储数据信息
的,方法
的功能修改属性的数据
.- 在前面我们分析过
objc_class
结构体(里面存储类的信息), 里面有继承过来的isa
(指向元类), 有superClass
, 有bits
(存储属性, 实例方法, 代理, ro里有成员变量)结构体- 那
cache
结构体里面存储的是什么呢?
1: 我们先根据源码梳理下objc_class的结构图
objc_class结构树-c13422: 接下来我们来通过指针偏移试着看看cache里存储的是什么
cache查找LLDB流程, 图是月月的!关于缓存占用量的计算,有以下几点说明:
-
buckets() 是个列表, 怎么查找多个呢? 图是月月的!
- 利用指针偏移, 数组的
首地址
即第一个元素
的地址
例:
*($4 + 1)
- 利用列表特性
例:
$3.buckets()[1]
- 利用指针偏移, 数组的
-
经过lldb打印查看, 我们可以确认cache里存储的是
方法缓存
-
alloc申请空间时,此时的对象已经创建,调用的
实例方法
,都是shioccupied
+1, 扩容后会清空buckets
,occupied
为置为0
-
当有属性赋值时,会隐式调用
set
方法,occupied
也会增加
3: 我们详细的看下cache的结构
struct cache_t {//只复制了部分重要信息
//CACHE_MASK_STORAGE_OUTLINED: 模拟器 or macOS环境
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
// explicit_atomic: 原子性, 保证cache增删改差的线程安全
// 等同于struct bucket_t * _buckets;
// _buckets: 存放imp和sel
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask; //掩码
//CACHE_MASK_STORAGE_HIGH_16: 64位真机
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// 真机环境中, buckets和mask掩码存储在一起, 掩码在高16位(通过 << maskShift), buckets 存在剩余位
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;//暂时没有用到, 猜测没开发完, 不管它
static constexpr uintptr_t maskShift = 48;
//掩码后的其他位必须为零。 msgSend
//利用这些附加位来构造值
//在一条来自_maskAndBuckets的指令中`mask << 4`。
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// 应用于`_maskAndBuckets`的掩码,以获取存储桶指针。
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
// 确保我们有足够的位用于存储桶指针。
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
//非64为真机, 因为iOS9之后废弃32位, 所以我们不研究它
#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();
//重点可以获取buckets列表
struct bucket_t *buckets();
mask_t mask();// 获取我们的掩码(也可以理解为开辟最大空间)
mask_t occupied();// 记录当前缓存的方法数量
void incrementOccupied();// 操作`occupied++`, 即新插入一个bucket
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
unsigned capacity();// 容量
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
// 重新开辟空间, 一般在内存满3/4时扩容后调用
void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
// 插入新的bucket
void insert(Class cls, SEL sel, IMP imp, id receiver);
4: 总结: 我们来梳理下cache的工作流程
cache 缓存bucket流程图疑问解答 --Style_月月
-
1、
_mask
是什么?_mask
是指掩码数据,用于在哈希算法或者哈希冲突算法(cache_next)中计算哈希 下标,其中mask
等于capacity
(内存总容量) - 1 -
2、
_occupied
是什么?-
_occupied
表示哈希表中sel-imp
的占用大小 (即可以理解为分配的内存中已经存储了sel-imp
的的个数), -
alloc
后, 调用的实例方法
都会导致occupied
变化, 包括属性隐式实现的set方法
-
-
3、为什么随着方法调用的增多,其打印的occupied 和 mask会变化?
因为在
cache第一次
缓存bucket时,分配的空间是4
个,随着方法调用的增多,当存储的bucket
个数超过
capacity
(总容量)的3/4
, 就会进行capacity
翻倍, 并清理旧缓存
, 之后继续缓存新调用的实例方法
. -
4、bucket数据为什么会有丢失的情况?,例如2-7中,只有say3、say4方法有函数指针
原因是在扩容时,是将原有的内存全部清除了,再重新申请了内存导致的, 见
疑问3
解答 -
5、2-7中say3、say4的打印顺序为什么是say4先打印,say3后打印,且还是挨着的,即顺序有问题?
因为
cache_hash实现-c739bucket
的存储是通过哈希算法-cache_hash
计算下标的,其计算的下标有可能已经存储了sel,所以又需要通过哈希冲突-cache_next
算法重新计算哈希下标,所以下标并不是固定
的
cache缓存bucket流程图.jpg
网友评论