iOS九阴真经:五、nonPointerIsa分析

485 阅读4分钟

一、isa_t 的结构

下面是我们对类中的 isa 的探究,来看一下 isa 的源码实现:

image.png

image.png

objc_objectisa 是一个联合体,不是一个指针。这时候有个概念:非指针isanonPointerIsa)。

二、ISA_BITFIELD

isa_t 中有一个结构体成员,结构体成员中有一个宏定义:ISA_BITFIELD,点进去看一下:

image.png

在iOS中的架构是arm64,框红的地方,是真机模式下的。并且,ISA_BITFIELD 用到了位域。

以下是各个成员的注释:

  • nonpointer : 表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等。
  • has_assoc : 关联对象标志位,0没有,1存在。
  • has_cxx_dtor : 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象。
  • shiftcls : 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
  • magic : 用于调试器判断当前对象是真的对象还是没有初始化的空间。
  • weakly_referenced : 指对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。
  • unused : 标志对象是否正在释放内存。
  • has_sidetable_rc : 当对象引用技术大于 10 时,则需要借用该变量存储进位。
  • extra_rc : 当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到 has_sidetable_rc

先对 shiftcls 有一个概念的认识,在下面的探索中,主要会用到它。

三、isa 推导 class

通过 [SHPerson class] 可以拿到 SHPerson 的类对象。每个对象都有一个 isa,这个 isa 指向的是类对象,但通过 lldb 打印 SHPerson 实力对象的内存分布发现,第一块内存地址(也就是isa)和 [SHPerson class] 拿到的类对象的内存地址不一样。

 isa: 0x000021a100008175
 class对象: 0x0000000100008170

这是因为苹果做了一个掩码的操作,在 isa.h 文件中有一个 ISA_MASK 的宏定义(系统架构不一样,值也不一样,调试的时候根据运行环境选取不同的值),用 isa 的地址 & 上 ISA_MASK,就可以得出 class 对象的内存地址。

1. ISA_MASK 获取 calss 对象的内存地址

lldb打印如下:

(lldb) x/4gx person
0x1007170c0: 0x000021a100008175 0x0000000000000000
0x1007170d0: 0x0000000000000000 0xce7ed407c2c98f1d
(lldb) p/x [SHPerson class]
(Class) $3 = 0x0000000100008170 SHPerson
(lldb) p/x 0x000021a100008175 & 0x0000000ffffffff8ULL
(unsigned long long) $10 = 0x0000000100008170

isa 的地址 & 上 ISA_MASK后得出地址值和 [SHPerson class] 得出的地址值是一样的,都是 0x0000000100008170

2. isa 的位运算获取 calss 对象的内存地址

通过分析 isa_t 得知 ISA_BITFIELDshiftcls 是用来存储类指针的值,所以只要拿到 shiftcls,看看里面存放的值就可以知道类对象的内存地址。

观察到在 arm64(真机模式) 下,shiftcls 占33位,在前面共有3位(1+1+1),后面共有28位(6+1+1+1+19)。

所以,运算的步骤如下:

  1. 左移3位 -- 顶掉前面的3位,这时后面会空出3位,不足用0补。
  2. 右移31位 -- 后面本来有28位,加上用0补的3位,一共31位
  3. 左移28位 -- 将 shiftcls 归位。

平移的数位不是固定的,根据 ISA_BITFIELD 中的规定得算出来的。

lldb打印如下:

(lldb) x/4gx person
0x100722a40: 0x000021a100008175 0x0000000000000000
0x100722a50: 0x000000010080db90 0xcdb2bd35671d6859
(lldb) p/x 0x000021a100008175 >> 3
(long) $1 = 0x000004342000102e
(lldb) p/x 0x000004342000102e << 20
(long) $2 = 0x4342000102e00000
(lldb) p/x 0x4342000102e00000 >> 17
(long) $3 = 0x000021a100008170
(lldb) p/x [SHPerson class]
(Class) $4 = 0x0000000100008170 SHPerson
(lldb)

通过这种方式拿到类对象的内存地址和[SHPerson class]拿到的内存地址也是一样的,都是 0x0000000100008170