因为 ObjC 的 runtime 只能在 Mac OS 下才能编译,所以文章中的代码都是在 Mac OS,也就是 x86_64
架构下运行的,对于在 arm64 中运行的代码会特别说明。
在上一篇分析 isa
的文章从 NSObject 的初始化了解 isa中曾经说到过实例方法被调用时,会通过其持有 isa
指针寻找对应的类,然后在其中的 class_data_bits_t
中查找对应的方法,在这一篇文章中会介绍方法在 ObjC 中是如何存储方法的。
这篇文章的首先会根据 ObjC 源代码来分析方法在内存中的存储结构,然后在 lldb 调试器中一步一步验证分析的正确性。
方法在内存中的位置
先来了解一下 ObjC 中类的结构图:
-
isa
是指向元类的指针,不了解元类的可以看 Classes and Metaclasses
-
super_class
指向当前类的父类
-
cache
用于缓存指针和 vtable
,加速方法的调用
-
bits
就是存储类的方法、属性、遵循的协议等信息的地方
class_data_bits_t
结构体
这一小结会分析类结构体中的 class_data_bits_t bits
。
下面就是 ObjC 中 class_data_bits_t
的结构体,其中只含有一个 64 位的 bits
用于存储与类有关的信息:
在 objc_class
结构体中的注释写到 class_data_bits_t
相当于 class_rw_t
指针加上 rr/alloc 的标志。
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
它为我们提供了便捷方法用于返回其中的 class_rw_t *
指针:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
将 bits
与 FAST_DATA_MASK
进行位运算,只取其中的 [3, 47]
位转换成 class_rw_t *
返回。
在 x86_64 架构上,Mac OS 只使用了其中的 47 位来为对象分配地址。而且由于地址要按字节在内存中按字节对齐,所以掩码的后三位都是 0。
因为 class_rw_t *
指针只存于第 [3, 47]
位,所以可以使用最后三位来存储关于当前类的其他信息:
#define FAST_IS_SWIFT (1UL<<0)
#define FAST_HAS_DEFAULT_RR (1UL<<1)
#define FAST_REQUIRES_RAW_ISA (1UL<<2)
#define FAST_DATA_MASK 0x00007ffffffffff8UL
-
isSwift()
-
FAST_IS_SWIFT
用于判断 Swift 类
-
hasDefaultRR()
-
FAST_HAS_DEFAULT_RR
当前类或者父类含有默认的 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
方法
-
requiresRawIsa()
-
FAST_REQUIRES_RAW_ISA
当前类的实例需要 raw isa
执行 class_data_bits_t
结构体中的 data()
方法或者调用 objc_class
中的 data()
方法会返回同一个 class_rw_t *
指针,因为 objc_class
中的方法只是对 class_data_bits_t
中对应方法的封装。
// objc_class 中的 data() 方法
class_data_bits_t bits;
class_rw_t *data() {
return bits.data();
}
// class_data_bits_t 中的 data() 方法
uintptr_t bits;
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK)