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)。根元类 NSObject
root 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 继承自根类 NSObject
rootClass。根类 NSObject
继承自nil
,这也是为什么NSObject
是根类。
元类的继承
子类的元类
metal subClass 继承自父类的元类
metal superClass。父类的元类
metal superClass 继承自根元类
root metal class。根元类
root metal class 继承自根类NSObject
root 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
的内存地址是 首地址+0x8
cache
的内存地址是 首地址+0x10
bits
的内存地址是 首地址+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
存储了属性列表、方法列表、成员变量列表、协议列表等,类方法
存储在元类
里面。
后续还要对类进行分析,敬请期待。