- 006—iOS底层 - 类的结构(属性、成员变量、方法的探索)探索了类的结构,并探索了
bits
,本文我将讲述探索cache的过程。
###cache_t结构
与前面的文章006一样的方式,在objc
源码中,搜索struct objc_class
找到struct objc_class : objc_object
,找到objc_class
内部的cache_t cache
。进入cache_t
内部,知道其内部结构,成员变量的大小如下">引言
我们在前面的文章006—iOS底层 - 类的结构(属性、成员变量、方法的探索)探索了类的结构,并探索了bits
,本文我将讲述探索cache的过程。
###cache_t结构
与前面的文章006一样的方式,在objc
源码中,搜索struct objc_class
找到struct objc_class : objc_object
,找到objc_class
内部的cache_t cache
。进入cache_t
内部,知道其内部结构,成员变量的大小如下 - 脱离源码自定义cache
设计代码如下:QLPerson.h
中属性和方法声明如下:
2、解释:sel
:方法编号,相当于一本书的某一条目录imp
:函数指针地址,相当于该条目录对应的页码。通过此页码得到具体内容">总结
本文主要讲述探究cache_t
内部结构,并通过lldb调试
和脱离objc源码
环境模拟objc_class
内部的情况,探索cache_t
中bucket_t
、_occupied
和_maybeMask
的数值,看到cache_t
内的变化规律。下一篇文章将探索cache_t
的流程。
1、通过上面的探索,用图来表示
2、解释:sel
:方法编号,相当于一本书的某一条目录imp
:函数指针地址,相当于该条目录对应的页码。通过此页码得到具体内容
引言
我们在前面的文章006—iOS底层 - 类的结构(属性、成员变量、方法的探索)探索了类的结构,并探索了bits
,本文我将讲述探索cache的过程。
###cache_t结构
与前面的文章006一样的方式,在objc
源码中,搜索struct objc_class
找到struct objc_class : objc_object
,找到objc_class
内部的cache_t cache
。进入cache_t
内部,知道其内部结构,成员变量的大小如下
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 4字节
#if __LP64__
uint16_t _flags; // 2字节
#endif
uint16_t _occupied; // 2字节
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;// 8字节
};
以下代码省略...
1、#if __LP64__
为架构判断,
2、这些成员变量都是什么意思,分别代表什么?我们先通过lldb
调试去看看里面的值。
3、我们前面探索bits
的时候,用的是内存偏移0x20
得到的,同理我们可以通过偏移0x10
得到cache_t
###LLDB调试探索cache
话不多说,直接上操作过程。操作过程与探索bits
的操作流程差不多,我们在打印cache的时候,发现里面的值也无法获取,这时想到的,应该是cache_t内部的取值
函数。
####bucket_t
我们打印了cache_t
内容后,毫无收获,此时,应该在cache_t
内部寻找对应的功能方法(函数)
继续探索。在寻找过程中,发现bucket_t
是出现频率最高的,说明bucket_t
的重要性。下面我们将探索bucket_t
的内部结构,简化后的源码如下:
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__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
public:
static inline size_t offsetOfSel() { return offsetof(bucket_t, _sel); }
inline SEL sel() const { return _sel.load(memory_order_relaxed); }
inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {
uintptr_t imp = _imp.load(memory_order_relaxed);
if (!imp) return nil;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
SEL sel = _sel.load(memory_order_relaxed);
return (IMP)
ptrauth_auth_and_resign((const void *)imp,
ptrauth_key_process_dependent_code,
modifierForSEL(base, sel, cls),
ptrauth_key_function_pointer, 0);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
return (IMP)(imp ^ (uintptr_t)cls);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
return (IMP)imp;
#else
#error Unknown method cache IMP encoding.
#endif
}
...
void set(bucket_t *base, SEL newSel, IMP newImp, Class cls);
};
当我们看到熟悉的_sel
和_imp
知道bucket_t
内部主要存放着_sel
和_imp
,并提供了sel()
和imp()
函数,给外界传值。
我们继续lldb调试
,QLPerson.h
@interface QLPerson : NSObject
@property (nonatomic,copy) NSString *nickName;
@property (nonatomic,strong) NSString *strongNickName;
- (void)test1;
@end
main.m
中
QLPerson *person = [QLPerson alloc];
NSLog(@"%@",person);
lldb
调试步骤如下:
1、buckets()
返回的不只一个bucket
,buckets
是一种哈希链表
,其内部元素是无序的
,新的数据通过一定的算法,找到下标,将新数据插入。
2、在打印buckets
内部数据的时候,取值跟数组一样,通过下标取值,一直找到_sel的value
和_imp的value
有值的时候,调用sel(),imp()
方法,找到person
内的test1
方法声明和实现。
脱离源码自定义cache
设计代码如下:
QLPerson.h
中属性和方法声明如下:
@interface QLPerson : NSObject
- (void)test1;
- (void)test2;
- (void)test3;
- (void)test4;
- (void)test5;
+ (void)test12;
@end
在main.m
中,通过提取objc源码
中的struct bucket_t
、cache_t
、chass_data_bits_t
、objc_class
的部分关键代码,加入前缀,组成一个模拟objc_class结构
的代码,用于对objc_class
内部的数据变化情况:
typedef uint32_t mask_t;
struct ql_bucket_t {
SEL _sel;
IMP _imp;
};
struct ql_cache_t {
struct ql_bucket_t *_buckets;
mask_t _maybeMask;
uint16_t _flags;
uint16_t _occupied;
};
struct ql_class_data_bits_t {
uintptr_t bits;
};
struct ql_objc_class {
Class isa;
Class superclass;
struct ql_cache_t cache; // formerly cache pointer and vtable
struct ql_class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
QLPerson *person = [QLPerson alloc];
Class pClass = [person class];
[person test1];
struct ql_objc_class *ql_class = (__bridge struct ql_objc_class *)(pClass);
struct ql_cache_t cache = ql_class->cache;
NSLog(@"_occupied:%u --- _maybeMask:%u",cache._occupied,cache._maybeMask);
for (mask_t i = 0; i < cache._maybeMask; i++ ) {
struct ql_bucket_t bucket = cache._buckets[i];
NSLog(@"SEL:%@---IMP:%p",NSStringFromSelector(bucket._sel),bucket._imp);
}
}
return 0;
}
解析:
1、ql_bucket_t
对应objc源码
如下,我们在之前的文章提到过,泛型
的最终类型为传入的具体类型,即此处的_sel
类型为SEL
,_imp
的类型为uintptr_t
,而我们之前打印过imp
的值,其实是一个地址,也就是说,按照我们的经验,我们直接讲_imp
的类型定义为IMP
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
2、ql_cache_t
对应objc源码
中的cache_t
struct cache_t {
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
1)_bucketsAndMaybeMask
实际类型为uintptr_t
2)union
中struct
和_originalPreoptCache
互斥,在__LP64__
机器架构中,用到的是struct
,又因为struct
内部只有基本类型变量,则内存大小为内部成员变量相加后的内存平移
得到的结果,因此cache_t
的结构变成了我们上面设计的ql_cache_t
,我们在探索_bucketsAndMaybeMask
的作用是通过store()
和load()
存取buckets
。因此我们简化流程,将_bucketsAndMaybeMask
直接替换成ql_bucket_t *
指针,最终得到ql_cache_t
的简化结构。
3、由于objc_class
是继承于objc_object
的,因此ql_objc_class
应包含Class isa
。若是不把isa
加进来,则会出现以下结果:_maybeMask
的值为无规律的数据,在此之前,我们打印过的_maybeMask
的值都是切确的3
,7
这样子的数据。_maybeMask
的错乱,说明了一个问题:指针混乱了。(我们将一个类转换成struct ql_objc_class
,类中有isa
在内的各种成员变量,若ql_objc_class
中不加入isa
,则将类的isa
开始的结构完整的赋值到缺少isa
的ql_objc_class
,导致数据错乱),正确的结果如下:
测试代码操作顺序
1、只保留test1
方法调用,结果如下:_occupied:1 --- _maybeMask:3
,并且得到test1
的imp
2021-07-05 22:19:34.680748+0800 QLObjcTest[46201:587343] -----[QLPerson test1]---
2021-07-05 22:19:34.681817+0800 QLObjcTest[46201:587343] _occupied:1 --- _maybeMask:3
2021-07-05 22:19:34.682090+0800 QLObjcTest[46201:587343] SEL:(null)---IMP:0x0
2021-07-05 22:19:34.682201+0800 QLObjcTest[46201:587343] SEL:(null)---IMP:0x0
2021-07-05 22:19:34.682628+0800 QLObjcTest[46201:587343] SEL:test1---IMP:0xb298
2、保留test1
、test2
方法调用,结果如下:_occupied:2 --- _maybeMask:3
,并且得到test1、test2
的imp
2021-07-05 22:21:03.659769+0800 QLObjcTest[46225:588419] -----[QLPerson test1]---
2021-07-05 22:21:03.660588+0800 QLObjcTest[46225:588419] -----[QLPerson test2]---
2021-07-05 22:21:03.660962+0800 QLObjcTest[46225:588419] _occupied:2 --- _maybeMask:3
2021-07-05 22:21:03.661402+0800 QLObjcTest[46225:588419] SEL:test2---IMP:0xb250
2021-07-05 22:21:03.661502+0800 QLObjcTest[46225:588419] SEL:(null)---IMP:0x0
2021-07-05 22:21:03.661771+0800 QLObjcTest[46225:588419] SEL:test1---IMP:0xb280
3、test1
、test2
、test3
方法调用,结果如下:_occupied:1 --- _maybeMask:7
,但只有test3
的imp
2021-07-05 22:21:43.370104+0800 QLObjcTest[46243:589189] -----[QLPerson test1]---
2021-07-05 22:21:43.370911+0800 QLObjcTest[46243:589189] -----[QLPerson test2]---
2021-07-05 22:21:43.371076+0800 QLObjcTest[46243:589189] -----[QLPerson test3]---
2021-07-05 22:21:43.371333+0800 QLObjcTest[46243:589189] _occupied:1 --- _maybeMask:7
2021-07-05 22:21:43.371431+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x0
2021-07-05 22:21:43.371535+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x0
2021-07-05 22:21:43.371634+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x0
2021-07-05 22:21:43.371698+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x0
2021-07-05 22:21:43.371746+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x0
2021-07-05 22:21:43.371792+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x0
2021-07-05 22:21:43.372053+0800 QLObjcTest[46243:589189] SEL:test3---IMP:0xb228
4、test1
、test2
、test3
、test4
方法调用,结果如下:_occupied:2 --- _maybeMask:7
,但只有test3、test4
的imp
2021-07-05 22:24:43.640023+0800 QLObjcTest[46279:591066] -----[QLPerson test1]---
2021-07-05 22:24:43.640809+0800 QLObjcTest[46279:591066] -----[QLPerson test2]---
2021-07-05 22:24:43.641000+0800 QLObjcTest[46279:591066] -----[QLPerson test3]---
2021-07-05 22:24:43.641100+0800 QLObjcTest[46279:591066] -----[QLPerson test4]---
2021-07-05 22:24:43.641524+0800 QLObjcTest[46279:591066] _occupied:2 --- _maybeMask:7
2021-07-05 22:24:43.641648+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x0
2021-07-05 22:24:43.641747+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x0
2021-07-05 22:24:43.641836+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x0
2021-07-05 22:24:43.641936+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x0
2021-07-05 22:24:43.642308+0800 QLObjcTest[46279:591066] SEL:test4---IMP:0xbe00
2021-07-05 22:24:43.642380+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x0
2021-07-05 22:24:43.642548+0800 QLObjcTest[46279:591066] SEL:test3---IMP:0xb1d0
5、test1
、test2
、test3
、test4
、test5
方法调用,结果如下:_occupied:3 --- _maybeMask:7
,但只有test3、test4、test5
的imp
2021-07-05 22:25:41.883301+0800 QLObjcTest[46303:591991] -----[QLPerson test1]---
2021-07-05 22:25:41.884102+0800 QLObjcTest[46303:591991] -----[QLPerson test2]---
2021-07-05 22:25:41.884218+0800 QLObjcTest[46303:591991] -----[QLPerson test3]---
2021-07-05 22:25:41.884274+0800 QLObjcTest[46303:591991] -----[QLPerson test4]---
2021-07-05 22:25:41.884324+0800 QLObjcTest[46303:591991] -----[QLPerson test5]---
2021-07-05 22:25:41.884578+0800 QLObjcTest[46303:591991] _occupied:3 --- _maybeMask:7
2021-07-05 22:25:41.884670+0800 QLObjcTest[46303:591991] SEL:(null)---IMP:0x0
2021-07-05 22:25:41.884723+0800 QLObjcTest[46303:591991] SEL:(null)---IMP:0x0
2021-07-05 22:25:41.884972+0800 QLObjcTest[46303:591991] SEL:test5---IMP:0xbe38
2021-07-05 22:25:41.885029+0800 QLObjcTest[46303:591991] SEL:(null)---IMP:0x0
2021-07-05 22:25:41.885180+0800 QLObjcTest[46303:591991] SEL:test4---IMP:0xbe08
2021-07-05 22:25:41.885232+0800 QLObjcTest[46303:591991] SEL:(null)---IMP:0x0
2021-07-05 22:25:41.885286+0800 QLObjcTest[46303:591991] SEL:test3---IMP:0xb1d8
省略号………….
由此规律,我们操作到test8
的时候,两者的值为_occupied:1 --- _maybeMask:15
,打印的sel
和imp
只有test8
有值,其他都是null
,为什么会这样呢?猜想,当类中的方法达到某个数量的时候,_occupied
和maybeMask
都会随着数量的变化而变化,那他们分别代表什么意思?