内存管理
说到iOS的内存管理,不仅仅是MRC、ARC这些大众所熟知的,还有一些内存高效管理的代表之一TaggedPointer
内存布局
首先来了解下 我们的内存布局总共分为五大区:
- 栈区
- 堆区
- 全局区(静态区.bss)
- 常量区(.data)
- 代码区(.text)
栈区 :
-
由编译器分配和释放,主要存储局部变量、方法参数、方法指针、函数等
-
内存地址:一般为0x7开头
-
定位方式:栈区的内存是通过sp寄存器直接去定位,所以查找速度很快
-
内存区域是连续的,所以大小有限制,但是不会产生内存碎片,安全可靠
堆区 :
-
由运行时分配内存,主要存储需要开辟空间存储的,比如alloc、new、block的copy等
-
内存地址:一般为0x6开头
-
定位方式:堆区的内存是先通过寄存器定位到包含内存空间的地址,然后通过地址定位到内存的位置,所以它相对栈区多了一步操作过程,速度也就不如栈区了
-
内存区域不连续,容易产生内存碎片,所以需要注意野指针、内存泄漏等问题
全局区.bss :
- 主要存储未初始化的全局变量、静态变量
- 内存地址:一般为0x1开头
常量区 .data :
- 主要存储初始化的全局变量、静态变量
- 内存地址:一般为0x1开头
代码段 .text :
- 主要存储程序运行的二进制代码
TaggedPointer
又称为小对象,主要是一些类型NSNumber、NSDate、NSIndexPath、Colors等等
首先来看一个普通对象指针结构,将它分解为二进制表示法
我们有64位,然而并没有真正的使用到所有这些位
一个真正的对象只有使用到中间的这些位,由于内存对齐要求的存在,右边的低位始终为0;而由于地址空间有限,左边的高位也始终为0;我们实际上不会用到2^64
因为高位和低位始终为0,所以我们从这些始终为0的位中选择一个位让它变成1,这可以让我们知道这不是一个真正的对象指针,这样可以将其他位赋予一些别的意义
这样的指针就称为TaggedPointer
iOS13之前分布的布局
这里为什么使用高位的1来区分TaggedPointer呢,实际是对msg_send的一个优化,为了通过一次比较来对taggedPointer和nil进行检查
iOS14之后的优化
优化后将tag标签移到低位,中间的有效负载成为正常的指针,这样和我们普通对象指针结构很相似。因为动态链接会忽略指针的前八位,这是由于一个Top Byte Ignore 的ARM特性
这样的作用是,它开启了TaggedPointer引用二进制文件中常量数据的能力(例如字符串或其他数据结构),否则他们将占用dirty memory(这里的内存很宝贵,后期会单出一篇来讲)
TaggedPointer指针的值不再是地址,而是真正的值,实际上它不是一个对象,而是一个披着对象皮的普通变量,而且它不需要malloc和free,在内存上读取有着3倍的效率,创建比之前快106倍
下面一个测试
不断的读写操作造成崩溃
当将nameStr改成test时则正常运行不会崩溃,因为这里已经变成TaggedPointer类型了
字符串长度在10以内都是TaggedPointer小对象类型,大于10是对象类型
它不受retain release ARC操作影响,可以看到它的强大之处了