在之前的文章中,我们提到了类对象,但是没有细说。今天一起来扒扒这里面有啥东西。
类对象,也就是我们平时所的Class
,其在Runtime
中的定义是结构体struct objc_class
的指针:
/// Object.mm line 33
typedef struct objc_class *Class;
而struct objc_class
继承至struct objc_object
(c++中的结构体可以继承,并且可以定义方法),这就说明类对象也是对象
。除了继承来的isa
成员,struct objc_class
还存储了父类,缓存信息以及类的具体信息:
1 |
|
下面就一起来看看该结构体的具体成员。参考下图:
isa - 我是谁
isa
是对象的心脏,它解决了对象是谁的问题。isa
的真正面目是union isa_t
:
1 |
|
也许你会疑惑,之前看到的struct objc_object
中的成员isa
是Class
类型,为啥这里又出现一个联合体。下面说说我的理解:
首先,看看这两种类型所占用的内存大小是否一样。
union isa_t
结构体有 3 个成员:
Class cls;
Class 本质上是指针,占 8 字节。uintptr_t bits;
uintptr_t
本质是unsigned long
的 typedef,占 8 字节。匿名结构体
这里使用了位域,加起来一共 64bit,也是 8 字节。
根据联合体 size 的大小计算规则,整个联合体的大小是 8 字节,和之前看到的 Class 类型的 isa 并无差别,它们占用的内存大小相同。
可以理解为对这 8 字节的内存解读方式的区别。如果按 Class 解读这 8 字节,那么这块内存就只能存储一个地址了;如果按这种怪异的方式解读,相比之下存储的信息就多了,具体见下图:
其次,为什么会设计出这个怪异的结构。
在上面的代码里,可以看到在arm64
架构下,虚拟地址的最大值为0x1000000000
,使用 33 位足够了。那么 8 字节(64 位)的空间只用 33 位是不是有点可惜,那么这种怪异的结构自然也就出来了。要知道在程序运行期间,可是有无限多个对象要创建的,在这里进行变态级的优化
,效果是非常可观的。
superclass - 我从哪里来
这个成员比较简单,记录了该类的继承结构,指向其父类对象。
cache - 我干的最频繁的事
这里以散列表的方式存储方法缓存,提高消息发送时的查找效率。
1 |
|
在这个散列表中的每一个元素bucket_t
,存储了一个方法的 SEL,和 IMP。
1 |
|
既然是散列表,就会牵扯到索引的计算和冲突解决。先来看看索引的计算:
1 |
|
这里有个注意点,一个数和mask&运算的结果<=mask
。mask 也间接反映了cache_t
中缓存方法的数量:
1 |
|
冲突的解决,这里直接采用当前索引加一或减一的方式:
1 |
|
bits - 我的方方面面
该字段存储了一个类相关的具体信息,比如方法列表、协议列表,属性等。
bits
的类型为class_data_bits_t
,它只是封装了class_rw_t
(类的可读写数据)和自定义的标识以及操作内部数据的方法。这个结构体只有一个成员bits
,占用 8 字节的内存。其中这个class_rw_t* data()
方法可以获取类的可读写数据:
1 |
|
看来这个bits
成员也存储了不止一种数据。有兴趣的可以可以查看objc-runtime-new.h
文件中FAST_
开头的宏定义。每一个宏和bits
进行&
运算都可以得到相应的信息。
class_rw_t
该结构存储了一个类的可读写信息。其中只读信息又独立出来,使用class_ro_t
结构表示,这个放到下节讲述。而我们平时书写的方法、属性以及协议也都存储在这里。
struct class_rw_t {
// ...
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
// ...
}
这三者的结构类似,都继承至list_array_tt
,而list_array_tt
是一个 C++模板。一个为存储元数据的通用实现。其中,Element
是元数据的类型,比如method_t
;List
是包含元数据的列表,比如method_list_t
。list_array_tt
类型的值有 3 种:
- 空
- 一个
List
指针 - 一个数组,元素是
List
指针
下面是详细的说明,建议认真查看。
1 |
|
总结来说,list_array_tt
,是一个容器,直接存储的类型需要为数组(typename List),然后数组里面的元素是元数据(typename Element),并提供相应的操作方法,如迭代器,添加新的Lists
等。
一个method_array_t
就是:
properties
和protocols
的List
和Element
分别是property_list_t - property_t
、protocol_list_t - protocol_ref_t
再来看看method_list_t
和property_list_t
以及protocol_list_t
。这三者是list_array_tt
的直接元素。其中method_list_t
和property_list_t
都继承至entsize_list_tt
。这是一个 C++模板,包含了两个类型参数一个非类型参数。这是一个具有non-fragile
特性1的数组通用实现。下面是具体的解读:
1 |
|
protocol_list_t
是一个单独实现的结构体。只包含了记录数量,内存地址成员,以及迭代方法等。
好了,容器解决了。再瞅瞅具体的元数据。
-
method_t
1
2
3
4
5struct method_t { SEL name; const char *types; MethodListIMP imp; }
method_t
描述了一个方法的基本信息,如:名称、编码类型2、具体实现。 -
property_t
1
2
3
4struct property_t { const char *name; const char *attributes; };
主要包含名称,以及对应的属性(类型,内存管理方式,对应实例变量名称等)。
-
protocol_ref_t
protocol_ref_t
是一个指针,指向struct protocol_t
。它也继承至objc_object
。主要有协议名称、遵循的协议列表、(可选)实例方法、(可选)类方法、实例属性等内容。
class_ro_t
class_ro_t
主要记录了一个类在编译期确定的信息,不能改变。
1 |
|
可以看到,这里使用的复杂数据类型,在class_rw_t
都出现过。理解起来应该比较轻松。其中ivar_list_t
是成员变量列表,它继承至entsize_list_tt
,其中的元素类型为ivar_t
。一个ivar_t
代表一个成员。
1 |
|
总结
Runtime
的学习就是理解其中的数据结构,以及如何使用这些数据结构。该篇和之前的理解 Objective-C 中的对象介绍了实例对象和类对象对应的结构,是后续学习的基础。共勉!