- 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字节#endifuint16_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;#elseexplicit_atomic<SEL> _sel;explicit_atomic<uintptr_t> _imp;#endifpublic: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_PTRAUTHSEL 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_XORreturn (IMP)(imp ^ (uintptr_t)cls);#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONEreturn (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 vtablestruct 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;#elseexplicit_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;#endifuint16_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:32021-07-05 22:19:34.682090+0800 QLObjcTest[46201:587343] SEL:(null)---IMP:0x02021-07-05 22:19:34.682201+0800 QLObjcTest[46201:587343] SEL:(null)---IMP:0x02021-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:32021-07-05 22:21:03.661402+0800 QLObjcTest[46225:588419] SEL:test2---IMP:0xb2502021-07-05 22:21:03.661502+0800 QLObjcTest[46225:588419] SEL:(null)---IMP:0x02021-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:72021-07-05 22:21:43.371431+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x02021-07-05 22:21:43.371535+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x02021-07-05 22:21:43.371634+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x02021-07-05 22:21:43.371698+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x02021-07-05 22:21:43.371746+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x02021-07-05 22:21:43.371792+0800 QLObjcTest[46243:589189] SEL:(null)---IMP:0x02021-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:72021-07-05 22:24:43.641648+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x02021-07-05 22:24:43.641747+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x02021-07-05 22:24:43.641836+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x02021-07-05 22:24:43.641936+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x02021-07-05 22:24:43.642308+0800 QLObjcTest[46279:591066] SEL:test4---IMP:0xbe002021-07-05 22:24:43.642380+0800 QLObjcTest[46279:591066] SEL:(null)---IMP:0x02021-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:72021-07-05 22:25:41.884670+0800 QLObjcTest[46303:591991] SEL:(null)---IMP:0x02021-07-05 22:25:41.884723+0800 QLObjcTest[46303:591991] SEL:(null)---IMP:0x02021-07-05 22:25:41.884972+0800 QLObjcTest[46303:591991] SEL:test5---IMP:0xbe382021-07-05 22:25:41.885029+0800 QLObjcTest[46303:591991] SEL:(null)---IMP:0x02021-07-05 22:25:41.885180+0800 QLObjcTest[46303:591991] SEL:test4---IMP:0xbe082021-07-05 22:25:41.885232+0800 QLObjcTest[46303:591991] SEL:(null)---IMP:0x02021-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都会随着数量的变化而变化,那他们分别代表什么意思?
