ISA分析到元类
之前文章iOS底层--对象本质&ISA分析探究了isa。
打印p的指针 0x00000001089c94f0,根据指针地址获取isa 0x011d800100008365 进行掩码内存偏移 0x00007ffffffffff8 得到 0x0000000100008360,po获取当前的类 XHPerson。
对得到的 0x0000000100008360继续分析,进行内存偏移 0x00007ffffffffff8 得到 0x0000000100008338,po获取当前的类也是 XHPerson。可以得出XHPerson:类会和我们的对象,无限开辟,内存不止有一个类。验证打印下面代码:
//MARK: - 分析类对象内存存在个数
void lgTestClassNum(void){
Class class1 = [XHPerson class];
Class class2 = [XHPerson alloc].class;
Class class3 = object_getClass([XHPerson alloc]);
Class class4 = [XHPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
// 结果
2021-06-21 15:23:32.247341+0800 002-isa分析[13938:273705]
0x100008360-
0x100008360-
0x100008360-
0x100008360
从打印来看 0x0000000100008360是类,0x0000000100008338不是类。它就是元类。
对元类进行x/4gx得到 0x00007fff88a2ac20 继续进行内存偏移 0x00007ffffffffff8,得到还是 0x00007fff88a2ac20,po得到 NSObject。
意味着当前的
NSObject 这个类的 isa 指向 NSObject 的 元类 。根据上面的分析可以的得出。
isa的走位图和继承链
- 对象 isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类
- 根类 isa -> 根元类 isa
isa走位
实例对象instance of Subclass 的isa指向类class。类class 的isa指向元类meta class。元类meta class 的isa指向根元类 NSObject(root metal class)。根元类 NSObjectroot meta class 的isa指向自己本身,形成闭环。
打印下面代码,
#pragma mark - NSObject 元类链
void lgTestNSObject(void){
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
// XHPerson元类
Class pMetaClass = object_getClass(XHPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"XHPerson元类 %@ - %p",psuperClass,psuperClass);
// XHTeacher -> XHPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(XHTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"XHTeacher元类 %@ - %p",tsuperClass,tsuperClass);
// NSObject 根类特殊情况
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"NSObject元类 %@ - %p",nsuperClass,nsuperClass);
// 根元类 -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"根元类 %@ - %p",rnsuperClass,rnsuperClass);
}
// 结果
2021-06-21 16:02:30.535430+0800 002-isa分析[14411:327183]
0x108b06850 实例对象
0x7fff88a2ac48 类
0x7fff88a2ac20 元类
0x7fff88a2ac20 根元类
0x7fff88a2ac20 根根元类
2021-06-21 16:02:30.535501+0800 002-isa分析[14411:327183] XHPerson元类 NSObject - 0x7fff88a2ac20
2021-06-21 16:02:30.535531+0800 002-isa分析[14411:327183] XHTeacher元类 XHPerson - 0x100008338
2021-06-21 16:02:30.535554+0800 002-isa分析[14411:327183] NSObject元类 (null) - 0x0
2021-06-21 16:02:30.535576+0800 002-isa分析[14411:327183] 根元类 NSObject - 0x7fff88a2ac48
苹果官方提供的图
superclass继承
类的继承
类subClass继承自父类superClass 。父类superClass 继承自根类 NSObjectrootClass。根类 NSObject继承自nil,这也是为什么NSObject是根类。
元类的继承
子类的元类metal subClass 继承自父类的元类metal superClass。父类的元类metal superClass 继承自根元类root metal class。根元类root metal class 继承自根类NSObjectroot class。
类的结构
内存偏移
分析类的结构之前,需要了解内存偏移,想要拿到类的内容时,需要通过内存偏移去获取。
普通指针
a和b都指向10,但是a和b的地址不一样,地址之间相差4字节,因为a和b都是int类型。
对象指针
p1和p2也是指针,分别指向XHPerson alloc创建的内存地址。&p1和&p2是指向p1和p2对象指针的地址。
数组指针
&c和&c[0]取出来的都是首地址。&c和&c[1]相差4个字节(地址之间差值,取决于存储的数据类型)。- 通过首地址+偏移量可以取出数组中的元素,偏移量也就是我们常说的
数组下标。 内存中首地址移动的字节数=偏移量*数据类型字节数
类结构分析
通过objc源码,可以大致知道objc_class内部大致有哪些东西,如下图所示:
isa:虽然此处注释了,但是objc_class继承自objc_object,所以objc_class肯定也是有isa的,占8字节。superclass:Class类型,本质是一个结构体指针,占8字节。cache:是cache_t类型,通过源码我们可以知道cache_t是结构体类型,结构体类型的内存大小是由其内部的属性来决定的。bits:是class_data_bits_t类型,无法获取其具体内存大小,只能通过上面说的内存偏移去获取bits。
cache内存大小计算
进到cache_t看看
发现
cache_t是一个结构体,除去static修饰的属性,其中影响其内存大小的只有上面截图中的两个:_bucketsAndMaybeMask和联合体 我们可以得出cache_t这个结构体的内存大小为8+8=16字节。
类里面的各个成员变量,成员变量的内存地址如下
isa的内存地址是首地址,前面已经探究了superclass的内存地址是 首地址+0x8cache的内存地址是 首地址+0x10bits的内存地址是 首地址+0x20
获取bits
今天主要对bits进行探究。
bits.data()应该存储数据,data()的类型是class_rw_t*,源码分析下class_rw_t类型,源码比较长,需要多看源码学会一种思想,这边是找方法定位关键代码
class_rw_t是结构体类型,提供了获取方法列表,属性列表,协议列表的方法。通过实例来验证下方法,属性,变量是不是在class_rw_t中,在XHPerson类中添加属性和方法 以及成员变量
进行lldb调试
属性探究
类中声明的属性
name,hobby存储在属性列表preperty_list_t里,p $7.get(0)和p $7.get(1)获取相应的属性。
对象方法探究
- 对象方法存储在
XHPerson类中的方法列表method_list_t里。 - 类方法没有在
XHPerson的方法列表method_list_t里。 p $7.get(x)在方法列表中获取不到具体的值,因为method_t中进行了处理,通过big()获取变量。
类方法探究
对象的方法是存储在类中,那么类方法可能存储在元类中。按照这个思路探究下
类方法存储在元类中的方法列表里。
总结
类中有isa、superclass、cache、bits 成员变量,在对bits 探究过程中发现bits存储了属性列表、方法列表、成员变量列表、协议列表等,类方法存储在元类里面。
后续还要对类进行分析,敬请期待。