一、isa
- OC中, 每一个对象都有一个isa指针
- 实例对象的isa指向类对象, 类对象的isa指向元类对象, 元类对象的isa指向基类的元类对象, 基类元类对象的isa指向基类元类对象本身

- 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
- 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息

- 想要通过isa获取类对象和元类对象的地址, 就需要使用
isa & ISA_MASK

那么arm64之后的isa究竟是什么样的?
二、使用char类型的变量存储三个bool变量的值
- 准备代码, 定义
Person
类, 并添加三个属性tall
、rich
、handsome

- 可以如下使用
Person
类

- 在上面的代码中, 三个属性分别占用一个字节, 所以一共占用了三个字节
- 而bool的结果只有
0
和1
, 实际上用1bite
的空间就可以存储bool
值(1字节=8bite) - 所以我们可以设置一个
1字节
的成员变量来存储这三个变量的值 - 修改
Person
中的代码, 移除三个变量, 添加三对get
和set
方法, 添加一个char
类型的成员变量_tallRichHandsome

char
类型在内存中只占用一个字节0b0000 0000
, 我们可以使用最后面的三个bite位
来存储tall、rich和handsome
的值
1、存值
- 首先我们初始化
_tallRichHandsome = 0b00000000

- 接着在
setTall:
方法中, 将传进来的tall
存到_tallRichHandsome
中最低位中 - 当
tall
的值是YES
时, 我们将_tallRichHandsome
的最低位设置为1 - 想要只改变二进制数据中的某一位为1, 只需要使用逻辑或(
|
)即可, 例如
0110 0100
|0000 0001
---------- // 逻辑或, 只要不为1, 结果就是1
0110 0101
- 所以
tall
的值是YES
时, 代码如下
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHandsome |= 1;
}else {
}
}
- 当tall的值为0时, 我们需要将
_tallRichHandsome
的最低位设置为0, 其他位不变, 此时我们需要使用逻辑与, 例如
0110 0101
|1111 1110
---------- // 逻辑与, 只要全是1, 结果才是1
0110 0100
- 所以
tall
的值是NO
时, 代码如下
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHandsome |= (0b00000001);
}else {
_tallRichHandsome &= (0b11111110);
}
}
- 同理,
rich
和handsome
存在第二位和第三位, 代码如下

- 我们知道
1
向左位移可以获取如下的值
// 位移
1<<0 = 0b00000001
1<<1 = 0b00000010
1<<2 = 0b00000100
- 通过取反又能获取下面的值
// 取反
~0b00000001 = 0b111111110
~0b00000010 = 0b111111101
~0b00000100 = 0b111111011
- 所以上面代码可以如下简化

2、取值
- (BOOL)isTall
方法中, 我们需要将_tallRichHandsome
最低位取出来- 而取值, 只需要将
二进制数据 & 0b00000001
即可, 例如
1001 1110
&0000 0001
----------- // 逻辑与, 只有全是1, 结果才是1
0000 0000
- 通过这种方式, 就可以取出
_tallRichHandsome
中的最低位
- (BOOL)isTall
{
return _tallRichHandsome = (1<<0);
}
-
同理,
isRich
和isHandsome
方法也是一样 -
通过逻辑与取出的值, 每一位上如果是
1
, 那么结果肯定大于0
, 例如0b00000001
、0b00000010
和0b00000100
-
此时说明存的值是
YES
, 否则就是NO
-
所以存值取值的代码如下

- 再次运行程序, 有如下结果

- 我们可以发现
handsome
的结果是4
, 这是因为取出来的数是0b00000100
- 只要在取出的结果前加上两个
!
即可, 此时运行的结果就是1

- 我们可以多试验几次, 可以发现结果没有问题, 此时说明值已经存取成功
三、使用位域存储三个bool变量的值
- 我们可以使用位域来存储是三个bool变量的值, 代码如下

- 定义了
_tallRichHandsome
结构体,_tallRichHandsome
中有三个成员变量, 这三个变量因为使用了位域, 所以每个变量只占用一个bite
, 所以_tallRichHandsome
的大小只有1个字节

-
因为使用了位域, 所以
tall
只占用第一位,rich
只占用第二位,handsome
只占用第三位 -
所以代码就可以如下改进, 这样看就方便了许多

- 运行代码, 有如下结果

- 我们可以看到
rich
的结果是-1
, 并不是1
- 这是因为, 从位域取出来的
rich
的结果只是0b1
, 而系统为了补全二进制位, 将1
当做了符号位, 就把rich
变成了0b11111111
, 所以结果就是-1

- 想要获取正确的结果, 只需要将三个变量分别占用
两个bite
即可, 此时再取出的就是0b01
或者0b00
, 当再次补齐的时候, 符号位就是0
而不是1

四、使用共用体存储三个bool变量的值
- 修改
Person.m
中代码,_tallRichHandsome
是一个共用体

- 可以看到
_tallRichHandsome
中有一个char
类型变量bits
和一个结构体
bits
占用一个字节,而结构体
使用了位域, 也只占一个字节, 所以共用体_tallRichHandsome
只占用一个字节- 使用
_tallRichHandsome
存储三个bool值时, 与上面的二、使用char类型的变量存储三个bool变量的值
方法相同

- 可以将共同点换成宏定义

- 然后使用宏替换掉位移代码

- 执行程序, 可以发现结果正确

五、查看isa源码
- 查看runtime源码, 可以看到isa的定义, 其中arm64就是iOS使用的

- 通过整理可以得出下面的代码

- 自从arm64开始,
isa
就是一个共用体
, 里面存储多个值, 而对象的地址也只是其中的一部分而已 - 在
isa
共用体中,shiftcls
描述的就是类型地址在bits
中存储的位置, 即4-36
位(isa共用体中的结构体没有实际意义,只是用来描述内存中每一部分的作用) - 已知
isa & ISA_MASK
才能获取到类对象
和元类对象
的地址 - 而
ISA_MASK
的值就是0x0000000ffffffff8
, 具体的二进制是
0b00000000111111111111111111111111111111111000
- 所以
isa & ISA_MASK
的值, 二进制结尾必然是三个0
- 我们打印一下
NSObject
和Person
的类对象以及元类对象的地址

- 可以发现类对象以及元类对象的地址中, 最后一位总是
0
或8
, 当16进制转为2进制时,8
会转为1000
,0
会转为0000
- 之所以有这样的结果, 就因为类对象以及元类对象的地址存储在共用体
isa
的4-36
位中
总结: 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
六、isa位域解释
nonpointer
0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
1,代表优化过,使用位域存储更多的信息
has_assoc
是否有设置过关联对象,如果没有,释放时会更快
has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
shiftcls
存储着Class、Meta-Class对象的内存地址信息
magic
用于在调试时分辨对象是否未完成初始化
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快
deallocating
对象是否正在释放
extra_rc
里面存储的值是引用计数器减1
has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable的类的属性中