0%

runtime

先说类的本质

我们都知道类的本质其实就是一个isa指针,我们直接看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct objc_object {
private:
isa_t isa;
//从这开始往下不用看了,都是方法
public:

// ISA() assumes this is NOT a tagged pointer object
Class ISA();

// getIsa() allows this to be a tagged pointer object
Class getIsa();

// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
//、、、下面的省略

isa_t 的isa又是什么?

1
2
3
4
5
6
7
8
9
10
11
12
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};

原来就是一个union共用体,包含cls, bits, ISA_BITFIELD, 我们展开ISA_BITFIELD看一下
在arm64结构下,

1
2
3
4
5
6
7
8
9
10
11
# if __arm64__
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19

加上注释吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct {
// 0代表普通的指针,存储着Class,Meta-Class对象的内存地址。
// 1代表优化后的使用位域存储更多的信息。
uintptr_t nonpointer : 1;

// 是否有设置过关联对象,如果没有,释放时会更快
uintptr_t has_assoc : 1;

// 是否有C++析构函数,如果没有,释放时会更快
uintptr_t has_cxx_dtor : 1;

// 存储着Class、Meta-Class对象的内存地址信息
uintptr_t shiftcls : 33;

// 用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6;

// 是否有被弱引用指向过。
uintptr_t weakly_referenced : 1;

// 对象是否正在释放
uintptr_t deallocating : 1;

// 引用计数器是否过大无法存储在isa中
// 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
uintptr_t has_sidetable_rc : 1;

// 里面存储的值是引用计数器减1
uintptr_t extra_rc : 19;
};

cls的内存分布可以参考我的另外一片文章, 类的本质

看过我分类的本质也会发现,class_rw_t中的methods是二维数组的结构,并且可读可写,因此可以动态的添加方法,并且更加便于分类方法的添加。参考 category 源码可以发现,attachList函数内通过memmove 和 memcpy两个操作将分类的方法列表合并在本类的方法列表中。那么此时就将分类的方法和本类的方法统一整合到一起了。
其实一开始类的方法,属性,成员变量属性协议等等都是存放在class_ro_t中的,当程序运行的时候,需要将分类中的列表跟类初始的列表合并在一起的时,就会将class_ro_t中的列表和分类中的列表合并起来存放在class_rw_t中,也就是说class_rw_t中有部分列表是从class_ro_t里面拿出来的。

类的初始信息本来其实是存储在class_ro_t中的,并且ro本来是指向cls->data()的,也就是说bits.data()得到的是ro,但是在运行过程中创建了class_rw_t,并将cls->data指向rw,同时将初始信息ro赋值给rw中的ro。最后在通过setData(rw)设置data。那么此时bits.data()得到的就是rw,之后再去检查是否有分类,同时将分类的方法,属性,协议列表整合存储在class_rw_t的方法,属性及协议列表中。

希望对您有所帮助,您的支持将是我莫大的动力!