sa 指针从一个“纯粹的内存地址”变成了一个“高度压缩的复合数据结构”,这标志着 Apple 从“追随标准 C 结构”向“极致利用硬件性能”的转型。
以下是 isa 指针经历的三个主要阶段:
1. 早期阶段:纯指针(Plain Pointer)
在 32 位环境(如早期 iPhone 和 Intel Mac)下,isa 的结构非常简单。
- 本质: 它就是一个标准的 C 语言指针,大小为 4 字节(32位)。
- 内容: 直接存储类对象(Class)的内存地址。
- 缺点: 极其浪费。在 32 位或后来的 64 位空间里,内存地址通常不需要占用所有的位(Bits),剩下的位全部填零,没有任何作用。
2. 现代阶段:非指针型 isa(Non-pointer isa)
随着 iPhone 进入 64 位架构(A7 芯片以后),Apple 引入了 Non-pointer isa 技术。这是 isa 历史上最大的变革。
- 本质:
isa变成了一个 64 位的共用体(Union) 。它不再仅仅是一个地址,而是一个“位域(Bitfield)”。 - 核心理念: 既然 64 位地址空间大得离谱,真正有效的地址可能只需要 30 多位,剩下的 30 多位不如存点别的东西。
这 64 位里藏了什么?(以 ARM64 为例):
indexed(1 bit): 标志位。0表示是纯指针,1表示是 Non-pointer isa。has_assoc(1 bit): 对象是否有关联对象(Associated Objects)。如果没有,销毁对象时会快很多。has_cxx_dtor(1 bit): 是否有 C++ 析构函数或 ARC 的清理逻辑。shiftcls(33 bits): 核心部分。存储类对象的地址。需要通过掩码(Mask)运算才能取出来。magic(6 bits): 用于调试器判断对象是否初始化完成。weakly_referenced(1 bit): 对象是否被弱引用(Weak)过。deallocating(1 bit): 对象是否正在被销毁。has_sidetable_rc(1 bit): 引用计数溢出标志。如果 1 为则表示引用计数太大,存不下了,需要借用外部哈希表(SideTable)。extra_rc(19 bits): 性能神作。直接在isa里面存引用计数!
3. 进化带来的工程收益
A. 性能的质变:不再频繁查表
在“纯指针”时代,如果你执行 retain/release,Runtime 必须去维护一张巨大的全局哈希表(SideTable)来增减引用计数,这涉及到加锁操作,非常耗时。
在 Non-pointer isa 时代,引用计数直接存在 isa 的 extra_rc 位里。修改引用计数变成了一个简单的原子位运算,速度提升了数十倍。
B. 内存开销减小
由于 isa 内部记录了是否有关联对象和弱引用的状态,当一个对象销毁(dealloc)时,Runtime 可以通过位运算快速判断是否需要清理关联对象或清空弱引用表。如果这些标志位都是 0,销毁路径会极速缩短。
C. 安全性增强(ISA Signing)
在最新的 A12 及以后的芯片上,Apple 引入了 指针身份验证(PAC) 。isa 指针在存储时会被“签名”,使用时再验证。这防止了黑客通过篡改 isa 指针来实施跳转攻击,从硬件层面加固了 Objective-C 的动态性。
总结
| 特性 | 早期 isa | 现代 Non-pointer isa |
|---|---|---|
| 占用空间 | 32/64 bit | 64 bit |
| 存储内容 | 仅 Class 地址 | 地址 + 引用计数 + 状态标志位 |
| 修改引用计数 | 查表 + 加锁 | 直接修改位域(Atomic) |
| 销毁速度 | 较慢(全量检查) | 极快(根据标志位按需清理) |