05.深入探索Class的结构
一、内存平移
我们先通过下面的例子了解一下内存平移
int nums[4] = {1, 2, 3, 4};
NSLog(@"数组nums的地址:%p\n", &nums);
for (int i = 0; i < 4; i++) {
NSLog(@"第%d个元素是%d地址是%p", i + 1, nums[i], &nums[i]);
}
NSLog(@"\n");
int *numsPointer = nums;
NSLog(@"数组nums的指针:%p\n", numsPointer);
for (int i = 0; i < 4; i++) {
NSLog(@"偏移%d,取值%d,地址是%p", i, *(numsPointer + i), (numsPointer + i));
}输出:

nums一个元素4字节,指针偏移1次4字节。所以这里可以通过从数组首地址开始偏移的方式进行取值操作。
同理对Class的内存结构我们是否也能进行内存平移来取值呢?
二、Class的结构内存计算
在Class的源码中我们发现核心的数据结构是这样的,暂时忽略其中的方法,因为不影响Class的内存结构。
方法都存在方法区
-
ISA
superClass
cache
bits
说明
ISA(结构体指针)
父类(结构体指针)
方法缓存
类的具体信息
大小(字节)
8
8
16
8
三、objc_class: cache_t 的内存结构
我们忽略掉方法,剩下的核心数据结构如下
3.1 小补充:LP64数据模型
在里面我们可以看到 __LP64__,这里代表的是LP64数据模型。现今所有64位的类Unix平台均使用LP64数据模型。
TYPE
LP32
ILP32
LP64
ILP64
LLP64
含义
指long和pointer是32位的
指int,long和pointer是32位的
指long和pointer是64位
指int,long,pointer是64位
指long long和pointer是64位的
CHAR
8
8
8
8
8
SHORT
16
16
16
16
16
INT
16
32
32
64
32
LONG
32
32
64
64
32
LONG LONG
64
64
64
64
64
POINTER
32
32
64
64
64
3.2 内部联合体的内存结构
联合体包含了两部分:结构体、指针。我们已知arm64下指针占用8字节。我们来看下结构体需要占用多少
-
_maybeMask
_flags
类型
uint32_t
uint16_t
大小(字节)
4
2
结构体大小 8 字节,联合体大小为8字节
3.3 结构总结
-
_bucketsAndMaybeMask
union
说明
指针(做什么的还需要研究)
联合体
大小(字节)
long类型 8
8
cache_t大小为16字节
四、objc_class : class_data_bits_t 的内存结构
核心数据结构如下
我们发现内部只有一个指针bits,那么如何通过它获取到类的详细信息呢?
我们发现这样一个函数,通过bits与上FAST_DATA_MASK可以获得class_rw_t。和我们找ISA的过程比较类似。
那么它是做什么的呢?
FAST_DATA_MASK在
LP64下的定义
五、class_data_bits_t : class_rw_t
rw: read-write
5.1 class_rw_t的内存结构
5.2 一些重要函数
这里我们会看到一些眼熟的函数,在methods() properties() protocols()中我们发现了另一个重要的类型class_rw_ext_t
六、class_data_bits_t : class_rw_t : class_rw_ext_t
6.1 list_array_tt
method_array_t/property_array_t/protocol_array_t 都是继承于 list_array_tt 的,通过泛型来完成不同的定义。
源码核心部分:
method_array_t
property_array_t
protocol_array_t
6.2 class_ro_t
源码核心部分:
七、LLDB调试Class内存结构
结构图

有这样的一个类
7.1 成员变量
获取
class_data_bits_t
这里通过内存偏移的方式找到class_data_bits_t,如前文所述:bits所在位置的偏移为8+8+16=32字节。
那么我们理论上可以通过对象地址+0x20计算出它的位置。
通过
data()方法获取class_rw_t
通过
properties获取属性列表容器
取出
list
取出
ptr
取出
ptr内的值
通过下标取出
属性
7.2 实例方法列表
前面一些相似的步骤我们省略掉
从
class_rw_t -> methods获取方法列表容器
取出
list
取出
ptr
取出
ptr中的数据method_list_t
获取方法数量
通过c++函数
get()与big()单个获取类的实例方法这里除了我们自己定义的一些实例方法,还看到了一些列属性自动生成的
set、get方法,以及析构函数
7.3 协议列表
前面一些相似的步骤我们省略掉
从
class_rw_t -> protocols获取协议列表容器
取出
list
取出
ptr
取出
ptr中的数据protocol_list_t
这里可以发现,里面的list中存了一个指针,并不是完整的一个数据结构。我们找到了这样的定义:
typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped
获取
protocol_t *
读取数据
7.4 成员变量
前面一些相似的步骤我们省略掉
从
class_rw_t -> ro()获取成员变量列表容器
class_ro_t -> ivars获取成员变量列表
从成员变量列表中获取成员变量
7.5 类方法
类方法我们需要到元类中进行查找
获取元类类信息
获取元类方法列表(类方法)
输出方法信息
Last updated
Was this helpful?