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 的,通过泛型来完成不同的定义。

源码核心部分:

  1. method_array_t

  1. property_array_t

  1. protocol_array_t

6.2 class_ro_t

源码核心部分:

七、LLDB调试Class内存结构

结构图

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?