Autorelease
一个对象调用
autorelease
方法,就会被自动添加到最近的自动释放池autoreleasepool
,只有当自动释放池被销毁的时候,才会执行release
方法,进行释放。
- 下文源码是当前最新版本,跟之前版本会有点差别,没有了特别牛的
POOL_SENTINEL
(哨兵对象),改为了POOL_BOUNDARY
(边界对象),可能更好理解一点。 - 研究源码也可以看
1. 入口
runtime 的
NSObject.mm
文件中autorelease
方法
- (id)autorelease { return ((id)self)->rootAutorelease();}objc_object::rootAutorelease(){ return rootAutorelease2();}objc_object::rootAutorelease2(){ return AutoreleasePoolPage::autorelease((id)this);}static inline id autorelease(id obj){ id *dest __unused = autoreleaseFast(obj); return obj;}复制代码
2. AutoreleasePoolPage
-
关键词:
EMPTY_POOL_PLACEHOLDER
- 当一个释放池没有包含任何对象,又刚好被推入栈中,就存储在TLS(Thread_local_storage)中,叫做空池占位符
POOL_BOUNDARY
- 边界对象,代表
AutoreleasePoolPage
中的第一个对象
- 边界对象,代表
-
结构:
class AutoreleasePoolPage { magic_t const magic; // 当前类完整性的校验 id *next; pthread_t const thread; // 当前类所处的线程 // 双向链表 父子指针 AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat; static size_t const SIZE = PAGE_MAX_SIZE; // 4096}复制代码
- 一个 poolPage 对应于一个线程
AutoreleasePoolPage
是以 双向链表 的形式连接起来的,其中parent
child
分别是父结点(指向父类poolPage的指针)和子结点(指向子类poolPage的指针)- 一个 poolPage 的大小 是 4096 字节。其中56 bit 用来存储其成员变量,剩下的存储加入到自动释放池中的对象。
- 调用
autorelease
的对象 - 声明在
@autoreleasepool{}
中的对象
- 调用
- next 指向最新添加进来的对象所处的位置。
autoreleaseFast()
AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) {return page->add(obj);} else if (page) {return autoreleaseFullPage(obj, page);} else {return autoreleaseNoPage(obj);}复制代码
-
hotPage()
获取当前正在使用的 poolPage -
如果 page 存在,并且未满,直接进行添加
-
如果 page 存在,但是当前 poolPage 已满:
-
根据 page 遍历其所有的子 page,直到找到一个未满的子 page
-
否则就根据最后一个子 page,创建一个新的 page
do { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page);} while (page->full());复制代码
-
设置为 hotPage
setHotPage(page);
-
添加
return page->add(obj);
-
-
如果 page 不存在
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);setHotPage(page); page->add(POOL_BOUNDARY);return page->add(obj);复制代码
- 创建第一个 page,第一个 page 是没有父结点的
- 设置为当前正在使用的 page
- 添加边界对象,初始化的第一个poolPage 存储的第一个对象,必定是边界对象
POOL_BOUNDARY
- 添加对象
page -> add()
添加对象
id *ret = next; *next++ = obj;复制代码
- 获取 next 指针所处位置,并向上移动
- 把对象添加到 next 所处位置
小结:
一个对象,调用 autorelease
方法,实际是把该对象加入到当前线程正在使用的 autoreleasePoolPage
的栈中,但它怎么释放呢?ARC 下唯一能看到 autorelease
的就是入口函数 main.m
中的 @autoreleasepool{}
。
3. @autoreleasepool{}
int main(int argc, char * argv[]) { @autoreleasepool { return 0; }}复制代码
-
使用
clang -rewrite-objc main.m
重新编译,结果:struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} void * atautoreleasepoolobj;};int main(int argc, char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; return 0; }}复制代码
@autoreleasepool{}
实际调用的是两个方法objc_autoreleasePoolPush
和objc_autoreleasePoolPop
- objc_autoreleasePoolPush 方法内部实际就是 2.1,
autoreleaseFast()
函数,不过,参数是POOL_BOUNDARY
边界对象 - 每创建一个自动释放池,就会在当前线程的 poolPage 的栈中先添加一个边界对象,然后把池中的对象添加进去,直至栈满,创建子 page,继续添加。
objc_autoreleasePoolPop()
-
该方法内部实际调用的是
AutoreleasePoolPage
的pop()
方法,主要代码如下static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; // 根据指针 token 获取token所在的 page page = pageForPointer(token); stop = (id *)token; /* * 如果stop 不是边界对象,进行其他处理 * 如果stop 是边界对象,进行下面的步骤,来释放 */ // 释放栈中的所有对象,直至stop, // 此时,stop是边界对象,即 创建 autoreleasepool 时 // 添加的第一个对象 page->releaseUntil(stop); if (page->child) { // hysteresis: keep one empty child if page is more than half full // 不足一半满, 删除所有的子 page, // 否则 删除所有的孙 page if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } }}复制代码
-
stop 是边界对象,即创建
autoreleasepool
时 page 栈中添加的第一个对象 -
page->releaseUntil(stop)
方法,会对 page 栈中 stop 上面的所有对象调用objc_release(obj)
方法,除了边界对象POOL_BOUNDARY
-
kill()
方法,会删除当前 page 及其所有的子 page, 即page->child = nil;
-
小结:
- 对象释放的过程:首先找到当前对象所处的 AutoreleasePoolPage,然后获取到创建 page 时添加的第一个边界对象
POOL_BOUNDARY
,边界对象之后的添加进来的所有对象调用release
,进行释放。 - 创建自动释放池
@autoreleasepool{}
的过程:获取到正在使用的 AutoreleasePoolPage,首先添加边界对象POOL_BOUNDARY
,如果有其他对象,则进行添加。最后进行释放,同上1。
总结:
-
autoreleasepool
机制跟栈一样,每创建一个,就将其推入栈中,而清空autoreleasepool
,相当于将其从栈中弹出 -
一个对象调用
autorelease
方法,就会进入最近的autoreleasepool
,也就是栈顶的那个。 -
自动释放池的创建:
-
长时间在后台运行的任务,入口函数(
main.m
),系统自动创建autoreleasepool
-
主线程或GCD中的线程,会自动创建
autoreleasepool
, 关系是一对多的关系,即一个线程可以有多个autoreleasepool
-
人为手动创建
@autoreleasepool{}
- 程序不是基于 UI framework,如命令行
- 创建大量的临时对象,降低内存峰值(最常用)
创建了新线程(这个待定)
-
-
事件开始前,系统会自动创建
autoreleasepool
, 然后在结束时,进行drain
,对释放池内的所有对象执行release
方法
参考:
-
理论:
-
实践: