众所周知:一个AutoreleasePool对应一个RunLoop,一个RunLoop对应一个线程。但一个RunLoop可以包含多个AutoreleasePool。

本篇大致聊一聊AutoreleasePool:

本质:

AutoreleasePool的本质就是延迟 release 方法的调用。

MRC环境,可以通过调用 autorelease 来延迟内存的释放

ARC环境,甚至可以完全不知道 autorelease 也能管理好内存

 

ARC环境下:

  • 以 alloc / init / new / copy / mutableCopy 开头的初始化方法:系统会在 调用方法的外围 加上内存管理代码 retain / release,所以其在作用域结束的时候就会被释放
  • 以 其他 开头的初始化方法:系统会在 方法内部 自动加上 autorelease 方法,被注册到 AutoreleasePool 中,等到Pool dealloc时才释放

工作原理:


系统会在 RunLoop 每个运行循环之前(entry/beforeWaiting) 执行 autoreleasePoolPush 操作,会创建一个新的Page:在 当前Pool 的 next 位置插入一个Pool_Sentinel(哨兵对象),并返回其内存地址  poolToken,表示 新Pool 的起始位置。


  • push哨兵对象 /  autorelease 对象 :都会调用 autorelease Fas t(id obj) 来执行具体的 插入操作 :

当前Page存在且没满:直接添加至next指向位置;


当前Page存在且已满:创建一个新的Page,添加至新的page中;


当前Page不存在:创建第一个Page,添加至新page中。


 


在  RunLoop 结束之前,执行Pool的 autoreleasePoolPop 操作,传入poolToken,对哨兵对象之后添加的所有对象执行release。


  • Pop(token) 沿着parent方向走到哨兵对象
  1. 使用  pageForPointer  获取当前  token  所在的  AutoreleasePoolPage
  2. 调用  releaseUntil  方法释放 栈中的 对象,直到  stop
  3. 调用  child  的  kill  方法(当前Page里的对象超过一半时,会保留child)

releaseUntil:(详情请看)


当next指针不为stop时,从当前page开始回溯, 当前page不为空时回移next指针,挨个对象release


 


 


内部结构:


class AutoreleasePoolPage { // 用C++实现的类
  magic_t const magic;                 // 校验Page的完整性
  id *next;                            // 指向将要添加新对象(Autorelease对象、哨兵对象)的空闲位置
  pthread_t const thread;              // 当前页所在的线程
  AutoreleasePoolPage * const parent;  // 双向链表中指向上一个节点,第一个结点的 parent 值为 nil
  AutoreleasePoolPage *child;          // 双向链表中指向下一个节点,最后一个结点的 child 值为 nil
  uint32_t const depth;                // 深度,从0开始,往后递增1
  uint32_t hiwat;                      // high water mark
  ...
};

AutoreleasePool并没有单独的结构,而是由 若干个Page 以 双向链表 的形式组合而成的 指针堆栈

每个Page对象回开辟4096个字节内存(也就是虚拟内存一页的大小)

系统会根据保存对象地址数量动态的 增加 和 删除 page 节点

-每个Page除了Page自身的成员变量外,剩下的空间用 begin 和 end 用标识,存放 autorelease对象 和 哨兵对象 的内存地址

-当next指针作为游标指针:指向begin时,表示page为空;指向end时,标识page已满

-当一个page的空间被占满时,会新建一个page对象,连接链表,后来的autoRelease对象在新的page加入。

 


需要手动创建自动释放池:

  • 编写不基于UI框架的程序,如命令行工具
  • 编写一个创建许多临时对象的循环
  • 生成辅助线程(必须在线程开始执行后立即创建Pool,否则将泄露对象。非Cocoa程序创建线程时才需要)
  • 长时间在后台运行的任务。

enumerateObjectsUsingBlock内部有autoReleasePool

参考:

黑幕背后的Autorelease(后面的黑魔法看不懂>_<)

AutoreleasePool探索学习(转化为.cpp文件)

iOS探究 - autorelease 和 autoreleasepool(写得不错)

自动释放池的前世今生 ---- 深入解析 autoreleasepool (Page相关操作源码分析和结构示意图)