OC 底层探索 04、类的结构分析 & isa & bits
本文内容主要对isa指向流程 和 类的结构以及类中 bits 进行探索。
一、类与 isa
运行 objc源码工程,main.m 文件中断点打在 objc2,读取对象 objc2 的内存如下:
图中,我们发现两个不同的 地址,他们的值都是 MyPerson。这是为何呢?--> 元类
我们继续读取内存,如下图:
结合以上内容,我们可以得出 objc2 --> MyPerson --> MyPerson --> NSObject
即:isa 对象 --> 类 --> 元类 --> NSObject 类
上图中,通过 NSObject.class 看到: NSObject --> NSObject
有2个不同地址的 NSObject ,它又是为何呢?内存中会存在多个 NSObject 对象吗?
不会!类对象在内存中只存在一份!!!
我们以 MyPerson 简单验证类对象在内存中只有一个:
alloc 多次,class 地址相同,只有一个。
isa 指向流程
从上面第二幅图中可以观察到(注意黄线标识部分),NSObject 类,内存读到 0x00000001003ee0f0 后,开始指向自己。
--> 实例对象 --> 类 --> 元类 --> NSObject 根元类 --> NSObject 根元类
isa 指向图:
小小注意点:
实例对象之间时不存在关系的,所谓的父子继承是针对类而言。
NSObject(Root class) 的父类是nil,NSObject 的元类是根元类(root),根元类的父类是 NSObject,根元类的元类是自身。
OC - NSObject 万物之源!!!
二、类与对象
1、objc_object 和 objc_class
通过源码查找:
objc_object - 对象 : --> OC 的 NSObject 底层 --> objc_object 结构体
objc_class - 类 :--> 继承自 objc_object
所有对象、类、元类都有 isa,它们是来源于继承的 objc_object 里面的 isa.
对象 --> objc_object
类 --> objc_class
而 objc_class --继承自--> objc_object --> class 也是个对象 --> 万物皆对象
--> 引申:类是什么?类是元类的实例对象。
问题:objc_object 和 对象的关系?
从上面我们可知,所有的对象本质上都是基于 objc_object 为模板创建的。
OC 的底层其实就是 C/C++,OC 相当于一层封装。以结构体 objc_object 为模板,创建了所有 OC对象,进而提供给我们使用。苹果的大特质就是封装啊。
本质 - 万物皆 objc_object !!!
objc_object / objc_class / object / NSObject / isa 关系图:
三、类的结构分析
通过源码查看结构:
在继续探索类之前我们要先简单介绍下指针和内存偏移。
1、扩展 - 指针和内存偏移
1、普通指针
指针地址不同,浅拷贝 也称值拷贝。
2、对象指针
objc1 objc2 指针 指向 [MyPerson alloc] 开辟的空间-内存地址;
&objc1 &objc2 指针的地址 --> 二级指针。
3、数组指针
如上图,*p 指针指向arr,即 arr 首地址。通过指针偏移,可一次获取到 arr 中数据。
2、类 的结构和内容
isa:继承 objc_object 的isa ,8字节
superclass:Class 类型 指针,8字节
catch_t :结构体,结构体大小的计算我们在内存对齐中有介绍,它是内部全部属性的大小,且要遵循内存对齐原则。
catch_t 的大小计算:
if -->
bucket_t: struct bucket_t * 指针类型 8字节;
mask_t: uint32_t 4 字节;
elif -->
uintptr_t:指针类型 8字节;
mask_t:uint32_t 4 字节;
其他 -->
__flag:unsigned short uint16_t 2 字节
_occupied:unsigned short uint16_t 2 字节
8 + 4 + 2 + 2 = 16 字节
3、类中的信息 - bits
1、找到类信息
1、直接拿到类的首地址方式(lldb):bits 前面有三个变量 8+8+16 = 32.
p/x MyPerson.class
x/4gx MyPerson.class
2、计算 bits 地址 --> 首地址平移 32 字节:
16进制,32 即进位 2 --> 16进制地址 第二位 + 2
3、通过源码 1250行 (见下图),得知 bits 中有个 ‘class_rw_t *data’ 的data。
拿到 data:$2->data().
读取到data:p *$4
查看 bits 流程 --> 类的首地址 + 32 --> bits 地址 --> (class_data_bits_t *)bits地址 --> bits.data() --> (class_rw_t )data{}
如上图,我们看到了 $5 中的一些信息,但是我们的属性方法列表在哪里呢?
2、bits
MyPerson 代码(这里为了便于区分后来将定义的 name 改为 propName了):
@interface MyPerson : NSObject { NSString *instName; } @property (nonatomic, copy) NSString *propName; - (void)funcObjcTest; + (void)funcClassTest; @end @implementation MyPerson - (void)funcTest { } + (void)funcClassTest { } @end
class_rw_t
点击进去,比较长,一直滑到下面,如图:
由源码我们可知,class_rw_t 中有 方法、属性、协议 列表 。
1、property_list 属性列表
根据源码,我们这里进行 property() 的查询:
propety_list 中:只有一个 “name” 属性存在。
2、method_list 方法列表 - 实例方法
p $21.get(4) error 数组越界。
method_list 中:propName 的 setter/getter 方法和 实例方法 funcTest。setter/getter 方法由系统自动帮我们生成。
.cxx_destruct:OC 封装于C++底层,默认添加。
从上可知,我们的实例变量和类方法并不在 property() 和 method() 中,继续探索。
3、class_ro_t 实例变量
结构体 class_rw_t 中 set_ro --> class_ro_t
class_ro_t
通过源码,可以猜测实例变量存在 ivars 中,我们做如下验证作操作:
如上,我们可知,实例变量和属性变量的_propName 都在 ivars 中。属性变量、成员变量简介.
我们继续寻找类方法在哪里。
通过文章上面的isa 探索部分,我们知道 实例指向类,类指向元类;实例方法在类中,那么类方法是否在元类中呢?
4、验证类方法位置
如上,我们通过 MyPerson.class 的 isa,拿到元类,然后进行地址平移 32 字节,之后操作如图,果然,类方法在其中找到。
同时,这里也可验证我们上面所探究的 isa 流向。
总结:
isa:
实例 --> 类
类 --> 元类
元类 --> 根元类
根元类 --> 根元类
方法:
实例方法 --> 类中
类方法 --> 元类中
变量:
属性变量 --> propety_list
成员变量/_属性变量 --> ivars