① bits(整数)
② cls(类指针)
③ ISA_BITFIELD(位域)
union 全貌
声明:uintptr_t bits;
把这 8 字节当作一个普通的 64 位无符号整数来读写。
整体操作,不关心内部哪个 bit 代表什么。
64 bit,全部是一个数字
bit 63 ←────────────────────────────────────────────────────────────────── bit 0
典型用法:提取 Class 指针
Class cls = (Class)(bits & ISA_MASK);

ISA_MASK = 0x0000000ffffffff8ULL(arm64)

用位运算一次性拿到 Class 地址,比逐位域读快。
Runtime 里 getIsa()ISA() 都走这条路。
声明:private: Class cls;
把这 8 字节当作一个 ObjC Class 指针来解读。
但它是 private,你不能直接访问,必须走 setClass / getClass
同样是这 8 字节,但解读成指针值
bit 63 ←────────────────────────────────────────────────────────────────── bit 0
为什么要 private + PAC?

在 arm64e(A12+)上,Class 指针是被 PAC(Pointer Authentication Code)签名的。
高位会被写入一段签名 hash,直接读裸值会拿到一个"脏"指针,无法使用。

必须走:
setClass(cls, obj) → 签名后写入
getClass(true)   → 验签后返回干净指针
getClass(false)  → 不验签(某些内部快速路径)
声明:struct { ISA_BITFIELD; };
把这 8 字节按 bit 切成若干段,每段有各自的名字和含义。
这是 nonpointer isa 的核心设计:用一个指针的空间,存了 N 种信息。
64 bit 按字段着色
bit 63 ←────────────────────────────────────────────────────────────────── bit 0
标志位 (1 bit)
shiftcls (33 bits)
magic (6 bits)
引用计数 (20 bits)
unused (1 bit)
bit 0nonpointer 1 bit 1 = 这是 nonpointer isa(塞了额外信息);0 = 纯裸指针(旧格式)
bit 1has_assoc 1 bit 对象是否有关联对象(objc_setAssociatedObject),释放时要清理
bit 2has_cxx_dtor 1 bit 类是否有 C++ 析构函数或 ObjC .cxx_destruct,决定释放路径
bit 3~35shiftcls 33 bits Class 指针右移 3 位后存在这里(低 3 位对齐保证为 0,存其他信息)
bit 36~41magic 6 bits 固定值 0x3b,调试时用于校验 isa 是否合法,防止误读野指针
bit 42weakly_referenced 1 bit 对象是否被弱引用指向,释放时要去 weak table 置 nil
bit 43unused 1 bit 预留位,当前未使用
bit 44has_sidetable_rc 1 bit 引用计数是否溢出到 SideTable(extra_rc 装不下时置 1)
bit 45~63extra_rc 19 bits 内联引用计数,存的是「真实引用计数 - 1」,最大能存 524287
union 的规则只有一条:所有成员共享同一块内存,大小取最大成员。

这里三个成员都是 8 字节,所以 union 就是 8 字节。
改了其中任何一个成员,另外两个读出来的值也跟着变——因为本来就是同一块。
三种视角,同一块内存
63
0
uintptr_t bits → 把这 64 bit 读作一个整数,做位运算最快
Class cls  → 把这 64 bit 读作类指针,必须 PAC 验签,所以 private
ISA_BITFIELD → 把这 64 bit 按字段拆分,每个 bit 有独立含义
验证:写 bits,读位域

isa.bits = 0x001d800100000001ULL;

此时直接读位域:
isa.nonpointer == 1 ✓ bit 0 = 1
isa.magic == 0x3b  ✓ bit 36~41
isa.extra_rc == 0  ✓ bit 45~63 全 0

同一块内存,只是换了解读方式。