数据结构
Class 就是结构体 objc_class
内部结构如下,需要注意的是objc_class继承自objc_object,那么里面就应该有一个isa。
| 参数名 | 类型 | 作用 |
|---|---|---|
isa |
Class |
isa指针,指向元类 |
superclass |
Class |
父类 |
chache |
cache_t |
方法缓存,大小由内部成员决定 |
bits |
class_data_bits_t |
存储了一些数据 |
data |
class_rw_t |
获取bits里存储的data |
1、isa
具体请看 isa详解
2、superclass
看变量名就知道这是父类对象,不需要过多解释
3、cache 方法缓存
cache顾名思义是缓存
| 参数名 | 类型 | 大小 | 作用 |
|---|---|---|---|
_buckets |
数组 |
8字节 | 存储一个个结构体bucket_t |
_mask |
mask_t,实际就是uint32_t |
4字节 | 掩码 |
_occupied |
mask_t,实际就是uint32_t |
4字节 | 已缓存的数量 |
接下来看bucket_t是什么,bucket_t有2个成员变量_imp和key,这不用多说肯定是方法和关键值。那么我们就可以猜想,是不是能根据key获取到对应的IMP,进行方法调用?至此也确定了cache里缓存的是方法。
4、bits 和 data
可以看到bits是一个64位的数据段,那么里面存了什么数据呢?
在bits下面紧接了一个data的声明,返回的正是bits里面的数据。但是类型是class_rw_t?
4-1 class_rw_t 结构
methods方法、properties属性、protocols协议,都是我们平常在声明类的时候能看到的,感觉相当熟悉。但是里面还有一个class_ro_t类型的成员变量ro引起了我的注意
4-2 class_ro_t 结构
ro里也有方法、属性、协议,还有实例变量,这是为什么呢?
4-3 探索 class_rw_t
LGPerson如下
执行代码打印获取到LGPerson类的结构,可以直接看到isa和superClass。但是怎么获取class_rw_t和class_ro_t 呢?
通过isa地址向右偏移32位可以获取到bits
输出methods,看到数量为4,存储了我们声明的sayHello方法以及nickName的setter、getter方法,但是并没有看到sayHappy方法?
输出properties可以看到我们声明的nickName属性,符合我们的预期
输出protrols,里面是空的,也符合预期
至此,对比LGPerson文件我们还没有看到sayHappy方法和成员变量hobby,猜测它们又被存储在哪里呢?来输出ro看看
输出baseMethodList可以看到里面的内容和rw的methods是一样的,没有sayHappy方法
输出baseProperties,很可惜依旧没有找到hobby
输出ivars,可以看到count为2,单独输出以后我们可以看到hobby和_nickName都在其中
4-4、类方法存储
sayHappy方法到底去哪里了?sayHappy 和sayHello 区别在于 sayHello 是实例方法, sayHappy 是类方法。这说明了实例方法存储在类对象中,类方法不存储在类对象中。
那么类方法存在哪里呢?大胆猜测一下类方法会不会存在元类对象中呢?接下来去元类中寻找一下。
输出元类的方法列表
4-5、bits和ro数据重复问题
bits里面存储了方法列表、属性列表等成员变量,为什么还需要ro再存储一份?
objc_class结构中的data()方法可以返回bits的信息,那么我们便可以通过setData()向上查找调用方看看class赋值流程,也许就能解开这个问题。
找到编译期间执行的realizeClass方法,这里应该就是最初的地方。
但这里并没有对rw里面的列表赋值,编译期间rw的各个列表应该是空值!
那么rw的数据又是从哪里来的呢?这就涉及到了methodizeClass方法,就是它向rw中添加类的方法列表、协议列表、属性列表。
执行了methodizeClass后ro中的属性、对象方法,协议都添加到了rw中。这样在class_rw_t结构中可以拿到类的相关信息了。也就形成了以下结构:
虽然找到了实现,但是还是不知道为什么这么存呀。这就需要看看了realizeClass在什么时候调用了。这个时候,prepare_load_methods引起了我的注意
prepare_load_methods是加载分类时会调用的方法,结合methodizeClass我们会发现分类添加的方法统统都会加入到rw而不加入ro。ro保留着类最原始的数据,后续改变都无法侵入它!
总结
至此,疑问已经解开,类结构已经解析完毕。
结论如下:
.h文件声明属性会自动生成setter、getter、成员变量。- 实例方法存在类中,类方法存在元类中
- 原始方法会存在
ro中,分类方法存在rw中