众所周知:一个AutoreleasePool对应一个RunLoop,一个RunLoop对应一个线程。但一个RunLoop可以包含多个AutoreleasePool。
本篇大致聊一聊AutoreleasePool:
AutoreleasePool的本质就是延迟 release 方法的调用。
MRC环境,可以通过调用 autorelease 来延迟内存的释放
ARC环境,甚至可以完全不知道 autorelease 也能管理好内存
ARC环境下:
系统会在 RunLoop 每个运行循环之前(entry/beforeWaiting) 执行 autoreleasePoolPush 操作,会创建一个新的Page:在 当前Pool 的 next 位置插入一个Pool_Sentinel(哨兵对象),并返回其内存地址 poolToken,表示 新Pool 的起始位置。
当前Page存在且没满:直接添加至next指向位置;
当前Page存在且已满:创建一个新的Page,添加至新的page中;
当前Page不存在:创建第一个Page,添加至新page中。
在 RunLoop 结束之前,执行Pool的 autoreleasePoolPop 操作,传入poolToken,对哨兵对象之后添加的所有对象执行release。
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加入。
enumerateObjectsUsingBlock内部有autoReleasePool
参考:
黑幕背后的Autorelease(后面的黑魔法看不懂>_<)
AutoreleasePool探索学习(转化为.cpp文件)
iOS探究 - autorelease 和 autoreleasepool(写得不错)
自动释放池的前世今生 ---- 深入解析 autoreleasepool (Page相关操作源码分析和结构示意图)