内存管理方案技术
Tagged Pointer
:(标记指针),用来处理小对象NSNumber,NSDate、NSStringNonpointer_isa
:,非指针类型。间单来说就是64位的二进制数据,不是存的指针,其他位用来存储OC对象一些其他信息sideTables
:散列表,主要包括引用计数表
和弱引用表
内存管理的篇章会对上面的一些技术一一去做介绍,今天的文章主要是讲解Tagged Pointer
技术
WWDC视频
非常建议大家先去看一下苹果官方视频对Tagged Pointer
的介绍,视频地址WWDC视频,建议从15:28
的位置开始观看,从视频中我们得到了如下信息
- 什么是tagged Pointer?
在64位的地址中,我们并没有真正所有的使用这些位,我们只是在一个真正的对象指针中使用了中间的一些位,由于对齐的要求地位总是0,对象必须总是位于指针大小倍数的一个地址中,由于地址空间有限,所以高位总是位0,我们实际上不会用到所有的64位,由于高位和低位总是位0,所以我们可以把这些位置设置位1,这样就不是一个真正的指针。我们可以把这些位赋予其他的一些含义。我们把这样的地址称为
tagged Pointer
- Tagged Pointer专⻔⽤来存储⼩的对象,例如NSNumber和NSDate
- Tagged Pointer指针的值不再是地址了,⽽是真正的值。所以,实际上它不再是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。所以,它的内存并不存储在堆中,也不需要malloc和free
- 在内存读取上有着3倍的效率,创建时⽐以前快106倍。
Tagged Pointer数据混淆
在dyld加载类的时候,_read_images->initializeTaggedPointerObfuscator
我们可以看到每次程序启动的时候都会生成一个随机数
objc_debug_taggedpointer_obfuscator
,让后通过这个数进行编码和解码
编码_objc_encodeTaggedPointer
编码实际上做了异或的处理,在真机上做了一些不一样的处理。我们已x86_64的为例。
解码_objc_decodeTaggedPointer
编码解码举例,假如
objc_debug_taggedpointer_obfuscator = 1010 1010
原始的ptr= 0101 1110
,
编码: 0101 111
^ 1010 1010
= 0000 1010
解码: 1010 1010
^ 1010 1010
= 0101 1110
环境变量设置
我们可以通过设置环境变量设置,来控制是否进行混淆,如下图
xcode设置
OBJC_DISABLE_TAG_OBFUSCATION = YES
关闭混淆
Tagged Pointer 数据结构
在objc
源码找到下面的代码
通过是否是
tagged poniter
方法,_OBJC_TAG_MASK这个字段。在mac os下
_OBJC_TAG_MASK = 1
,也就是最低位为1,在iOS下define _OBJC_TAG_MASK (1UL<<63)
,经过调试发现在不同的真机版本下面,tagged pointer结构会有所不同,下面的结果是我在iOS,14.5下面lldb调试,以二进制打印地址
lldb调试,以二进制打印地址
(p6-juejin.byteimg.com/tos-cn-i-
- 0b表示二进制,最高位位1表示是tagged pointer类型。
0-1
表示类型:010 = 2
表示NSString
,011= 3
,表示NSNumber
,在objc源码里可以验证这一点
NSString
:3-6
位,表示字符串的长度。比如0010 = 2,字符串长度为2NSNumber
:3-6
位,表示表示存储的是int 、float、double类型
数据存储验证
真机14.5下,7-62
位用来存储真正的数据,下面我们来验证一下。
NSString
:7-62
:前面的0省略:0110 0010
0110 0001
。我们查看一下ASCII表
也就是字符串的a和b
NSNumber
:7-62
:前面的0省略,就是0110
的十进制刚好是6
数据结构总结
用一张图总结真机14.5
下的存储结构。
当然这个图只是我在14.5下面的存储,不同的版本位置可能不一样。有兴趣的可以去研究一下。
NSString 内存管理
那么是不是NSString
一定就是Tagged Pointer
类型呢。下面我们做一个测试。
- 通过initWithString,stringWithString 和stringWithFormat,initWithFormat分别初始化string
- 初始化的字符串的长度不同,
<9的情况
,和>9的情况
代码如下:
打印结果:
从上面的打印结果发现NSString的类型有3种。
__NSCFConstantString
:字符串常量,是一种编译时常量
,retainCount值很大,对其操作,不会引起引用计数变化,存储在字符串常量区
。直接通过@""、initWithString、StringWithString 初始化 -NSTaggedPointerString
:就是我们上面提到的tagged pointer
类型。这个是苹果的优化,但是字符串的长度不能太长。具体是NSString类型,还是Tagged Pointer类型可以参考下面有一张表。
__NSCFString
:这个是对象类型,会分配内存空间、增加引用计数,存储在堆上
总结
-
tagged pointer
实际上是苹果系统堆内存的优化,可用于(NSString 、NSNumber、NSDate)。它的数据直接存储在指针中 -
由于
tagged pointer
不是存储在堆上,它的存取更方便,效率更高 -
减少了不必要的浪费、节省内存空间。