1-11.【OC】【基础】isa 指针在现代 Objective-C 中经历了哪些演进?

2 阅读3分钟

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 为例):

  1. indexed (1 bit): 标志位。0 表示是纯指针,1 表示是 Non-pointer isa。
  2. has_assoc (1 bit): 对象是否有关联对象(Associated Objects)。如果没有,销毁对象时会快很多。
  3. has_cxx_dtor (1 bit): 是否有 C++ 析构函数或 ARC 的清理逻辑。
  4. shiftcls (33 bits): 核心部分。存储类对象的地址。需要通过掩码(Mask)运算才能取出来。
  5. magic (6 bits): 用于调试器判断对象是否初始化完成。
  6. weakly_referenced (1 bit): 对象是否被弱引用(Weak)过。
  7. deallocating (1 bit): 对象是否正在被销毁。
  8. has_sidetable_rc (1 bit): 引用计数溢出标志。如果 1 为则表示引用计数太大,存不下了,需要借用外部哈希表(SideTable)。
  9. extra_rc (19 bits): 性能神作。直接在 isa 里面存引用计数!

3. 进化带来的工程收益

A. 性能的质变:不再频繁查表

在“纯指针”时代,如果你执行 retain/release,Runtime 必须去维护一张巨大的全局哈希表(SideTable)来增减引用计数,这涉及到加锁操作,非常耗时。

Non-pointer isa 时代,引用计数直接存在 isaextra_rc 位里。修改引用计数变成了一个简单的原子位运算,速度提升了数十倍。

B. 内存开销减小

由于 isa 内部记录了是否有关联对象和弱引用的状态,当一个对象销毁(dealloc)时,Runtime 可以通过位运算快速判断是否需要清理关联对象或清空弱引用表。如果这些标志位都是 0,销毁路径会极速缩短。

C. 安全性增强(ISA Signing)

在最新的 A12 及以后的芯片上,Apple 引入了 指针身份验证(PAC)isa 指针在存储时会被“签名”,使用时再验证。这防止了黑客通过篡改 isa 指针来实施跳转攻击,从硬件层面加固了 Objective-C 的动态性。


总结

特性早期 isa现代 Non-pointer isa
占用空间32/64 bit64 bit
存储内容仅 Class 地址地址 + 引用计数 + 状态标志位
修改引用计数查表 + 加锁直接修改位域(Atomic)
销毁速度较慢(全量检查)极快(根据标志位按需清理)