iOS 内存原理

2023.02.18 更
引用计数是 iOS 内存管理的核心,strong 是对其直接应用,weak / 自动释放池 是对其间接应用。要理解 weak 和自动释放池,最有效的办法就是看 runtime 源码,理解 hashTable 和 hashMap 这两张数据结构表。其中 自动释放池 还和 runtime 有很大关系,这点需要串联下知识点。

没有经历过 MRC 年代,对 iOS 的内存管理的理解就不会那么顺畅。
MRC 年代的内存总是不好管理,所以 ARC 帮我们做了很多事情。ARC 做了很多事情让内存管理更加精准优秀外,也隐藏了很多内存管理的细节,也让这块知识点不容易啃食。
真正的内存管理,一定需要回到 MRC 下面去理解,根本思想是:谁创建、谁释放、谁引用、谁管理
内存释放的唯一途径是:引用计数 = 0
其中自动释放池做了 “谁创建谁释放” 里面的一部分。
ARC 帮我们做了 “谁创建、谁释放、谁引用、谁管理” 四个部分。
ARC 帮我们写了很多管理内存的代码,包括 autolease、retain、release 等。如果不理解 MRC 下面他们的含义,是不可能理解 iOS 内存管理的。
对于 autolease、autoleasepool、autoleasepoolpage 这些,是自动释放池部分,是 iOS 内存管理的一个面。

在 ARC 下,我们虽然不需要写 retain 和 release,不代表他们不存在了。只是编译器帮我们自动添加了,并且在合适的时间添加的。只有编译器也不行,在运行时也会进行内存的控制。在编译和运行时两方的协调控制下,才做到了引用计数及时 = 0,也只有计数 = 0,内存才正确释放。

ARC 内存不是绝对安全释放的,还牵涉到内存区,如果字符串定义到了堆区,释放是及时的,定义到了栈区和常量区,就不那么及时了(虽然引用计数 = 0,代码也不能在调用,但是真实内存还在)。
而且很可能还会因为代码原因导致引用计数永远不可能为 0,常见的就是循环引用,如 Block 的双向强引用,NSTimer 的双向强引用等等,这里都需要特别的破环。解决双向引用的问题,核心在于破环,只要有一个缺口,内存不可能不释放。
其中循环引用的环的查找,也有不少技巧。核心还是在于通过 runtime 来判断是否是强引用,然后通过广度遍历,来确定环的存在。

深入理解自己生成的对象,自己持有、非自己生成的对象,自己也能持有、不再需要自己持有的对象需要释放、非自己持有的对象无法释放,就能深入理解 iOS 的内存管理。
推荐《Objective-C 高级编程》,更推荐苹果开源的 runtime 源码。