iOS内存管理(Tagged Pointer技术)

1,590 阅读3分钟

内存管理方案技术

  • Tagged Pointer:(标记指针),用来处理小对象NSNumber,NSDate、NSString
  • Nonpointer_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

截屏2021-09-20 上午11.08.52.png 我们可以看到每次程序启动的时候都会生成一个随机数objc_debug_taggedpointer_obfuscator,让后通过这个数进行编码和解码

编码_objc_encodeTaggedPointer

截屏2021-09-20 上午11.12.59.png 编码实际上做了异或的处理,在真机上做了一些不一样的处理。我们已x86_64的为例。

解码_objc_decodeTaggedPointer

截屏2021-09-20 上午11.16.19.png 编码解码举例,假如objc_debug_taggedpointer_obfuscator = 1010 1010 原始的ptr= 0101 1110

编码: 0101 111 ^ 1010 1010 = 0000 1010

解码: 1010 1010 ^ 1010 1010 = 0101 1110

环境变量设置

我们可以通过设置环境变量设置,来控制是否进行混淆,如下图

截屏2021-09-20 上午11.26.42.png xcode设置

截屏2021-09-20 上午11.30.58.png OBJC_DISABLE_TAG_OBFUSCATION = YES 关闭混淆

Tagged Pointer 数据结构

objc源码找到下面的代码 截屏2021-09-20 下午2.37.49.png 通过是否是tagged poniter方法,_OBJC_TAG_MASK这个字段。在mac os下 截屏2021-09-20 下午2.40.15.png _OBJC_TAG_MASK = 1,也就是最低位为1,在iOS下define _OBJC_TAG_MASK (1UL<<63),经过调试发现在不同的真机版本下面,tagged pointer结构会有所不同,下面的结果是我在iOS,14.5下面lldb调试,以二进制打印地址

截屏2021-09-20 下午3.18.07.png lldb调试,以二进制打印地址 (p6-juejin.byteimg.com/tos-cn-i-

  • 0b表示二进制,最高位位1表示是tagged pointer类型。0-1表示类型:010 = 2表示NSString011= 3,表示NSNumber,在objc源码里可以验证这一点

截屏2021-09-20 下午3.01.48.png

  • NSString3-6位,表示字符串的长度。比如0010 = 2,字符串长度为2
  • NSNumber3-6位,表示表示存储的是int 、float、double类型

数据存储验证

真机14.5下,7-62位用来存储真正的数据,下面我们来验证一下。

NSString7-62:前面的0省略:0110 0010 0110 0001。我们查看一下ASCII表

截屏2021-09-20 下午3.30.41.png 也就是字符串的a和b

NSNumber :7-62:前面的0省略,就是0110 的十进制刚好是6

数据结构总结

用一张图总结真机14.5下的存储结构。

截屏2021-09-20 下午3.42.54.png

当然这个图只是我在14.5下面的存储,不同的版本位置可能不一样。有兴趣的可以去研究一下。

NSString 内存管理

那么是不是NSString一定就是Tagged Pointer类型呢。下面我们做一个测试。

  • 通过initWithString,stringWithString 和stringWithFormat,initWithFormat分别初始化string
  • 初始化的字符串的长度不同,<9的情况和>9的情况

代码如下:

NString.png

打印结果:

打印结果.png 从上面的打印结果发现NSString的类型有3种。

  • __NSCFConstantString:字符串常量,是一种编译时常量,retainCount值很大,对其操作,不会引起引用计数变化,存储在字符串常量区 。直接通过@""、initWithString、StringWithString 初始化 - NSTaggedPointerString:就是我们上面提到的tagged pointer类型。这个是苹果的优化,但是字符串的长度不能太长。具体是NSString类型,还是Tagged Pointer类型可以参考下面有一张表。

截屏2021-09-20 下午4.25.02.png

  • __NSCFString:这个是对象类型,会分配内存空间、增加引用计数,存储在堆上

总结

  1. tagged pointer实际上是苹果系统堆内存的优化,可用于(NSString 、NSNumber、NSDate)。它的数据直接存储在指针中

  2. 由于tagged pointer不是存储在堆上,它的存取更方便,效率更高

  3. 减少了不必要的浪费、节省内存空间。