1.objc_class结构的成员构成
从iOS-Objc类结构解析中可知类结构objc_class偏移16个字节
,可以得到cache_t cache
成员变量,cache中缓存方法
(即sel很imp
)
objc_class结构的成员构成如下
2.cache_t源码查看
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED//模拟器(x86_64)和macos(i386)
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16//真机(arm64)
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
// mask占用的位数:16位,除去mask其余字段占用位数:64-16=48位(maskZeroBits=4,bucket=44,)
static constexpr uintptr_t maskShift = 48;
// 在mask和bucket中间额外预留4位,必须为0
static constexpr uintptr_t maskZeroBits = 4;
// 存储的mask的最大值
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// bucket的遮罩 : bucket = _maskAndBuckets & bucketsMask;
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 //真机(32位)
// _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:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
unsigned capacity();
//....
从源码可知,objc4-781根据不同的架构
,对cache_t定义不同.
架构分析
macos
: i386
模拟器
: x86
真机
: arm64
CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
: 模拟器和macos
CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
: 真机(arm64)
CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
: 真机(32位)
3.bucket源码查看
struct bucket_t {
private:
#if __arm64__ //真机
//explicit_atomic 原子保护
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else //非真机
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
//....
}
3.lldb调试获取cache_t和bucket内容
3.1 定义类
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)sayHello;
- (void)sayCode;
- (void)sayMaster;
- (void)sayNB;
+ (void)sayHappy;
@end
@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
3.2 使用类
1. LGPerson *p = [LGPerson alloc];
2. Class pClass = [LGPerson class];
3. [p sayHello];
4. [p sayCode];
5. [p sayMaster];
3.3 通过lldb命令可以获取到cache_t的内容和结构
将断点设置在5行位置,可以进行lldb命令打印获得cache_t的内容如下图:
通过machoView打开target的可执行文件,在方法列表中查看其imp的值是否是一致的,如下所示,发现是一致的,所以打印的这个sel-imp就是LGPerson的实例方法
4.cache的写入流程
4.1 探寻思路:
-
- cache中的
_occupied
会随着方法的调用而递增
,由此我们可以探寻到_occupied增加的函数incrementOccupied
,其源码实现如下:
- cache中的
void cache_t::incrementOccupied()
{
_occupied++;
}
-
- 全局搜索
incrementOccupied
,找到有且仅有一处调用,cache_t::insert
- 全局搜索
-
- 在cache_t::insert中设置断点,可以找到insert的调用位置在
cache_fill
,而cache_fill的调用位置在lookupMethodInClassAndLoadCache
- 在cache_t::insert中设置断点,可以找到insert的调用位置在
总结: 由上述探索,初步可以得出cache的写入流程如下:
方法调用-->编译生成objc_msgSend-->根据类和sel传入lookUpImpOrForward函数中,查找imp-->快速查找-->快速查找没有找到imp,进入慢速查找,遍历方法列表-->找到imp后,cache_fill-->cache_t::insert-->修改cache_t内容
注意:_occupied修改值的情况
-
调用init方法,_occupied 会+1
-
当有属性赋值调用set方法,_occupied会+1
-
当有方法调用时,_occupied也会增加
-
重新分配内存,_occupied = 0归零
4.2 insert的执行逻辑
4.3 cache内存分配逻辑
objc底层cache_t::insert
的部分源码如下:
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
if (!capacity) capacity = INIT_CACHE_SIZE;//INIT_CACHE_SIZE=4
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // CACHE_END_MARKER= 1
//newOccupied+1<=capacity / 4 * 3 不处理
}
else {// newOccupied+1 > capacity / 4 * 3 需要扩容
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 扩容两倍 :INIT_CACHE_SIZE=4
if (capacity > MAX_CACHE_SIZE) { //MAX_CACHE_SIZE<<16,最多缓存MAX_CACHE_SIZE个bucket,当occupied占用到MAX_CACHE_SIZE*3/4个时会重新申请缓存,释放原来的空间,将原来的缓存丢弃
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;
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(sel, imp, cls);
return;
}
if (b[i].sel() == sel) {
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
cache_t::bad_cache(receiver, (SEL)sel, cls);
-
创建后
第一次调用方法
,insert时,_occupied = 0
并且isConstantEmptyCache = true
,向系统申请4个bucket的内存空间
-
每次在底层调用
objc_msgSend
发送的消息,则会优先查找cache里的bucket
,如果cache里没找到对应的imp,则进入慢速查找方法列表,找到后调用insert
方法 -
当
newOccupied+1<=capacity / 4 * 3
,直接执行bucket插入 -
当
newOccupied+1 > capacity / 4 * 3
,第一次扩容重新申请内存是第3次
进入insert方法时,需要扩容到capacity*2
个bucket的内存空间,同时释放原来的内存
,并将_occupied归零
(_occupied = 0) -
扩容最大可以到
MAX_CACHE_SIZE= 32
,最多缓存32个bucket, -
capacity =MAX_CACHE_SIZE = 32
时 当occupied占用到32*3/4=24
个时会重新申请内存
,释放原来的空间,将原来的缓存丢弃
4.4 新的bucket插入的逻辑
//获取buckets的首地址
bucket_t *b = buckets();
// mask = capacity - 1;
mask_t m = capacity - 1;
//cache_hash计算(sel & mask)首次尝试插入bucket存储的index位置,如果index位置被占用需要重新计算插入位置
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
//遍历buckets的地址空间,cache_next循环找一个位置存储新的bucket
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(sel, imp, cls);
return;
}
if (b[i].sel() == sel) {
//该位置被占用,且存储的bucket的sel就是我们需要缓存的sel,则不需要再重复存储,这种情况可能出现在多线程异步的情况下
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
-
获取
buckets的首地址
,计算mask (mask = capacity - 1
) -
cache_hash计算(
sel & mask
)首次尝试插入bucket存储的index位置
,如果index位置被占用需要重新计算插入位置 -
do-while循环(
cache_next(i, m)
)查找buckets中一个没有被占用的位置
,存储需要缓存的bucket;找到空位存储退出循环
或者找到已经缓存的bucket退出循环
其中cache_next的算法如下:
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask; //(将当前的哈希下标 +1) & mask,重新进行哈希计算,得到一个新的下标
}
4.5 真机下重新申请内存时,需要将cache的_maskAndBuckets更新(调用setBucketsAndMask),
为了优化内存,arm64的真机下mask值和buckets的地址共同占用64位的空间,在内存中存储位域如下图
所以我们在缓存buckets
时也需要进行一些位移操作
,具体实现如下:
5. 脱离源码探索cache_t
在已知底层结构的情况,可以将底层的objc_class
,cache_t
,bucket_t
结构仿照源码格式和类型自定义一个相似的结构体
,然后将类强转为我们自定义的结构
,就可清晰的看到底层结构
和变换过程
5.1 准备类和调用源码
- 定义LGPerson类
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)say1;
- (void)say2;
- (void)say3;
- (void)say4;
- (void)say5;
- (void)say6;
- (void)say7;
+ (void)sayHappy;
@end
@implementation LGPerson
- (void)say1{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say2{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say3{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say4{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say5{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say6{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say7{
NSLog(@"LGPerson say : %s",__func__);
}
+ (void)sayHappy{
NSLog(@"LGPerson say : %s",__func__);
}
@end
- 自定义结构体
objc_class
,cache_t
,bucket_t
,和调用代码
#import "LGPerson.h"
#import <objc/runtime.h>
typedef uint32_t mask_t;
struct lg_bucket_t {
SEL _sel;
IMP _imp;
};
struct lg_cache_t {
struct lg_bucket_t * _buckets;
mask_t _mask;
uint16_t _flags;
uint16_t _occupied;
};
struct lg_class_data_bits_t {
uintptr_t bits;
};
struct lg_objc_class {
Class ISA; //注意objc_class这个位置有个isa
Class superclass;
struct lg_cache_t cache;
struct lg_class_data_bits_t bits;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
Class pClass = [LGPerson class]; // objc_clas
[p say1];
[p say2];
struct lg_objc_class *lg_pClass = (__bridge struct lg_objc_class *)(pClass);
NSLog(@"(1)_occupied: %hu - _mask:%u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
// 打印获取的 bucket
struct lg_bucket_t bucket = lg_pClass->cache._buckets[i];
NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
[p say3];
NSLog(@"(2)_occupied: %hu - _mask:%u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
// 打印获取的 bucket
struct lg_bucket_t bucket = lg_pClass->cache._buckets[i];
NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
[p say4];
NSLog(@"(3) _occupied: %hu - _mask:%u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
// 打印获取的 bucket
struct lg_bucket_t bucket = lg_pClass->cache._buckets[i];
NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
[p say1];
[p say2];
NSLog(@"(4) _occupied: %hu - _mask:%u",lg_pClass->cache._occupied,lg_pClass->cache._mask);
for (mask_t i = 0; i<lg_pClass->cache._mask; i++) {
// 打印获取的 bucket
struct lg_bucket_t bucket = lg_pClass->cache._buckets[i];
NSLog(@"%@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
NSLog(@"Hello, World!");
}
return 0;
}
5.2 打印输出解析
-
_occupied
: 缓存的buckets被使用(存储bucket)的个数,也就是cache中缓存的方法个数
,对象创建时为0
,重新申请内存后会清零
,添加一个bucket后会_occupied++
-
_mask
: 申请的buckets总容量-1
(可存储bucket个数-1),首次申请4个bucket内存,此时_mask =4-1=3; -
重新申请buckets内存
,原来的内存会清空,_occupied清零
,cache中原缓存的bucket也会丢弃
-
新的bucket
插入顺序
,不是连续的,根据cache_hash
计算起始查询位置,循环cache_next
查找一个空位置存储