类的原理分析

类的原理分析

// MARK: OC对象之对象的本质

类的结构

首先查看类的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class : objc_object {
// 这几条是c++中禁止使用该函数的申明
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 从这里开始是类的成员变量 因为继承自objc_object 所以默认包含ISA
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags

...
}

bits 分析

存储着类的方法、属性、协议、成员变量等等
源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct class_data_bits_t {
friend objc_class;

// Values are the FAST_ flags above.
uintptr_t bits;
...
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}

const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
}
...
};

可以看出 class_data_bits_t 中有 class_rw_t 和 class_ro_t。
ro 是 readonly ,也就是只读数据。rw 是 可读可写数据。
在wwdc中有提到 ro 数据是 clean memory, 也就是干净的数据,可以被释放,需要的时候可随时从硬盘中读取,rw 是 dirty memory,也就是脏数据,存储着运行中产生的一些数据。

class_ro_t

class_ro_t存储了当前类在编译期就已经确定的属性、方法以及遵循的协议,里面是没有分类的方法的。那些运行时添加的方法将会存储在运行时生成的class_rw_t中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif

union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};

explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;

const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
}

class_rw_t

ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t中

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
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}

const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}

const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}

细看两个结构体的成员变量会发现很多相同的地方,他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容。

通过地址方法类的数据

打印类的内存

1
2
3
4
x/4gx YCPerson.class
--------------------
0x100008198: 0x0000000100008170 0x000000010036e140
0x1000081a8: 0x00000001003663a0 0x0000801c00000000

通过源码可知,按内存排列第二个8字节存储的是superclass,验证一下

1
2
3
po 0x000000010036e140
---------------------
NSObject

查看一下 cache_t 的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // typedef unsigned long uintptr_t; 8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask; // typedef unsigned short uint16_t; 2 字节
#if __LP64__
uint16_t _flags; // 2 字节
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8字节
};
}

由此可知 cache_t 内存大小为 16字节
综上 cache 的地址偏移为 16 字节,bits 的地址偏移为32位

使用 lldb 查看一下(代码接上面代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(lldb) p/x 0x100008198+0x20  # 首先将地址+32位计算出bits的地址
(long) $1 = 0x00000001000081b8
(lldb) p (class_data_bits_t *)$1 # 强转类型为 class_data_bits_t *
(class_data_bits_t *) $2 = 0x00000001000081b8
(lldb) p $2->data() # 获取一下 bits 的 data 方法
(class_rw_t *) $3 = 0x0000000100706950
(lldb) p *$3 # 打印一下,可以打印出正确的数据,说明地址偏移是没问题的
(class_rw_t) $4 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000200
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff8020fa88
}
(lldb)

获取对象的属性

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
(lldb) p $4->properties()
(const property_array_t) $5 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008148
}
arrayAndFlag = 4295000392
}
}
}

(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
ptr = 0x0000000100008148
}

(lldb) p $6.ptr
(property_list_t *const) $7 = 0x0000000100008148

(lldb) p *$7
(property_list_t) $8 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 1)
}
(lldb) p $8.get(0) // 通过get方法获取第0个元素
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb)

最终获取到name属性