关注仓库,及时获得更新:iOS-Source-Code-Analyze
因为 ObjC 的 runtime 只能在 Mac OS 下才能编译,所以文章中的代码都是在 Mac OS,也就是
x86_64
架构下运行的,对于在 arm64 中运行的代码会特别说明。
在上一篇分析 isa
的文章从 NSObject 的初始化了解 isa中曾经说到过实例方法被调用时,会通过其持有 isa
指针寻找对应的类,然后在其中的 class_data_bits_t
中查找对应的方法,在这一篇文章中会介绍方法在 ObjC 中是如何存储方法的。
这篇文章的首先会根据 ObjC 源代码来分析方法在内存中的存储结构,然后在 lldb 调试器中一步一步验证分析的正确性。
方法在内存中的位置
先来了解一下 ObjC 中类的结构图:
isa
是指向元类的指针,不了解元类的可以看 Classes and Metaclassessuper_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 的标志。
1 |
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags |
它为我们提供了便捷方法用于返回其中的 class_rw_t *
指针:
1 2 3 |
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]
位,所以可以使用最后三位来存储关于当前类的其他信息:
1 |
#define FAST_IS_SWIFT (1UL |
isSwift()
FAST_IS_SWIFT
用于判断 Swift 类
hasDefaultRR()
FAST_HAS_DEFAULT_RR
当前类或者父类含有默认的retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
方法
requiresRawIsa()
FAST_REQUIRES_RAW_ISA
当前类的实例需要 rawisa
执行 class_data_bits_t
结构体中的 data()
方法或者调用 objc_class
中的 data()
方法会返回同一个 class_rw_t *
指针,因为 objc_class
中的方法只是对 class_data_bits_t
中对应方法的封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 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); } |
class_rw_t
和 class_ro_t
ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct class_rw_t { uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; }; |
其中还有一个指向常量的指针 ro
,其中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; uint32_t reserved; const uint8_t * ivarLayout; const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; }; |
在编译期间类的结构中的 class_data_bits_t *data
指向的是一个 class_ro_t *
指针:
然后在加载 ObjC 运行时的过程中在 realizeClass
方法中:
- 从
class_data_bits_t
调用data
方法,将结果从class_rw_t
强制转换为class_ro_t
指针 - 初始化一个
class_rw_t
结构体 - 设置结构体
ro
的值以及flag
- 最后设置正确的
data
。