在之前的文章中分析了objc_class
中的isa
和bits
,现在本文就来分析剩下的cache
准备工作
- 定义一个
YPPerson
类
@interface YPPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)sayHello;
- (void)sayCode;
- (void)sayMaster;
- (void)sayNB;
+ (void)sayHappy;
@end
@implementation YPPerson
- (void)sayHello{
NSLog(@"YPPerson say : %s",__func__);
}
- (void)sayCode{
NSLog(@"YPPerson say : %s",__func__);
}
- (void)sayMaster{
NSLog(@"YPPerson say : %s",__func__);
}
- (void)sayNB{
NSLog(@"YPPerson say : %s",__func__);
}
+ (void)sayHappy{
NSLog(@"YPPerson say : %s",__func__);
}
@end
- 在
main.m
中定义LGPerson
类的对象p
,并调用其中的4个实例方法,在p
调用第三个方法处加一个断点
main.m
lldb调试
lldb调试
cache
属性的获取,需要通过pClass的首地址平移16
字节,即首地址+0x10获取cache的地址从源码的分析中,我们知道
sel-imp
是在cache_t
的_buckets
属性中(目前处于macOS环境),而在cache_t
结构体中提供了获取_buckets
属性的方法buckets()
获取了
_buckets
属性,就可以获取sel-imp
了,这两个的获取在bucket_t
结构体中同样提供了相应的获取方法sel()
以及imp(pClass)
通过machoView
打开target的可执行文件
,在方法列表中查看其imp
的地址值是否是一致的,如下所示,发现是一致的,所以打印的这个sel-imp
就是YPPerson
的实例方法
machoView验证
cache_t底层原理分析
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;
static constexpr uintptr_t maskShift = 48;
static constexpr uintptr_t maskZeroBits = 4;
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
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.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
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;
......
......
}
- 在
cache_t
结构体中有一个_occupied
,在运行函数的时候,这里的_occupied
会自增,通过cache_t结构体里面有一个函数incrementOccupied()
追踪到在cache_t::insert
中有_occupied+1
操作。
cache_t::insert
image.png
cache_t::insert
方法,理解为cache_t
的插入,而cache
中存储的就是sel-imp
-
根据
occupied
的值计算出当前的缓存占用量
,当属性未赋值及无方法调用
时,此时的occupied()
为0
,而newOccupied
为1
- 调用
init
方法,occupied
也会+1
- 当有
属性赋值
时,会隐式调用set
方法,occupied
也会增加 - 当有
方法调用
时,occupied
也会增加
- 调用
如果是
第一次
创建,则默认开辟4
个
if (slowpath(isConstantEmptyCache())) { //小概率发生的 即当 occupied() = 0时,即创建缓存,创建属于小概率事件
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE; //初始化时,capacity = 4(1<<2 -- 100)
reallocate(oldCapacity, capacity, /* freeOld */false); //开辟空间
//到目前为止,if的流程的操作都是初始化创建
}
- 如果缓存占用量
小于等于3/4
,则不作任何处理
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) {
// Cache is less than 3/4 full. Use it as-is.
}
- 如果缓存占用量
超过3/4
,则需要进行两倍扩容
以及重新开辟
空间
//扩容算法: 有cap时,扩容两倍,没有cap就初始化为4
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 扩容两倍 2*4 = 8
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
// 走到这里表示 曾经有,但是已经满了,需要重新梳理
reallocate(oldCapacity, capacity, true);
// 内存 扩容完毕
}
reallocate 开辟缓存空间
image.png
bucket赋值
根据
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_t流程图
cache_t流程图