前言
在 2013
年 9
月,苹果推出了 iPhone5s
,与此同时,iPhone5s
配备了首个采用 64
位架构的 A7
双核处理器,为了节省内存和提高执行效率,苹果提出了Tagged Pointer
的概念。对于 64
位程序,引入 Tagged Pointer
后,相关逻辑能减少一半的内存占用,以及3
倍的访问速度提升,100
倍的创建、销毁速度提升。
问题
我们先看看原有的对象为什么会浪费内存?
假设我们要存储一个 NSNumber
对象,其值是一个整数。正常情况下,如果这个整数只是一个 NSInteger
的普通变量,那么它所占用的内存是与 CPU
的位数有关,在 32
位 CPU
下占 4
个字节,在 64
位CPU
下是占 8
个字节的。而指针类型的大小通常也是与CPU
位数相关,一个指针所占用的内存在 32
位 CPU
下为4
个字节,在 64
位 CPU
下也是8
个字节。
所以一个普通的 iOS
程序,如果没有Tagged Pointer
对象,从 32
位机器迁移到 64
位机器中后,虽然逻辑没有任何变化,但这种 NSNumber
、NSDate
一类的对象所占用的内存会翻倍。如下图所示:
Tagged Pointer
为了改进上面提到的内存占用和效率问题,苹果提出了Tagged Pointer
对象。由于 NSNumber
、NSDate
一类的变量本身的值需要占用的内存大小常常不需要8
个字节,拿整数来说,4
个字节所能表示的有符号整数就可以达到 20
多亿(注:2^31=2147483648
,另外 1
位作为符号位),对于绝大多数情况都是可以处理的。
所以我们可以将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以,引入了Tagged Pointer
对象之后,64
位 CPU
下NSNumber
的内存图变成了以下这样:
-
Tagged Pointer
是专⻔⽤来存储⼩的对象,例如NSNumber,NSDate
等。 -
Tagged Pointer
指针的值不再是地址了,⽽是真正的值。所以,实际上它不再是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。所以,它的内存并不存储在堆中,也不需要malloc
和free
。 - 当指针不够存储数据时,就会使用动态分配内存的方式来存储数据。
- 在内存读取上有着
3
倍的效率,创建时⽐以前快106
倍。
总结
苹果将Tagged Pointer
引入,给64
位系统带来了内存的节省和运行效率的提高。Tagged Pointer
通过在其最后一个 bit
位设置一个特殊标记,用于将数据直接保存在指针本身中。因为Tagged Pointer
并不是真正的对象。
nonpointer
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
的类的属性中
参考链接:巧神:
深入理解Tagged Pointer