AutoReleasePool 自动释放池
AutoReleasePool
是OC的一种内存自动回收机制
,它可以将加入AutoReleasePool
中变量的release时机
--- 延迟。
当我们创建一个对象
的时候,正常情况下,变量会在超出其作用域的时候立即release
。如果将对象加入到自动释放池中,这个对象不会立即释放
,而是等到runloop休眠 或者 超出autoreleasepool作用域{}
之后才会被释放
。
- 1、从程序启动到加载完成,主线程对应的
RunLoop
会处于休眠状态,等待用户交互来唤醒RunLoop
。 - 2、用户的每一次交互都会启动一次
RunLoop
,用于处理用户的所有点击,触摸事件等。 - 3、
RunLoop
在监听到交互事件之后,就会创建
自动释放池,并将所有延迟释放的对象添加到自动释放池。 - 4、在一次完整的
RunLoop
结束之前,会向自动释放池中所有的对象发送release消息
,然后自动释放池。
在大致了解自动释放池的工作流程之后,我们一起来探索一下自动释放池。
在日常的开发中,我们见到的最多的自动释放池就是main
函数里面的自动释放池。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
*******
在这个@autoreleasepool Block 中,只包含了一行代码,这行代码将所有的事件、消息全部交给了`UIApplication`来处理。
⚠️⚠️⚠️ 注意:整个 iOS 的应用都是包含在一个自动释放池 Block 中的。
下面我们为了减少干扰代码,将自动释放池中的代码删除,只保留自动释放池。然后将main.m
文件转cpp
来探索一下,就像我们探索Block
一样(Block 底层原理(一)
)
int main(int argc, char * argv[]) {
@autoreleasepool {
}
}
**************
$ clang -rewrite-objc main.m
在main.cpp
文件中我们可以看到:
struct __AtAutoreleasePool {
///构造函数
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
///析构函数
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
}
我们会发现,main.m
里面的@autoreleasepool {}
变成了__AtAutoreleasePool __autoreleasepool
。而__AtAutoreleasePool
又是一个结构体;所以自动释放池是一个。
仔细观察__AtAutoreleasePool
这个结构体,会发现结构体两个函数:
1、构造函数 objc_autoreleasePoolPush()
,会在结构体初始化的时候调用;
2、析构函数 objc_autoreleasePoolPop()
,还在结构体析构的时候调用(也就是说在出了作用域后,会自动调用析构
)。
看到两个函数,不知道大家有什么想法,给我的感觉就是,这个一定跟有关,大家仔细品一品这两个函数名。
到这里,我们好像已经将自动释放池的面纱揭开了一点点,下面我们顺着这个思路继续探索。
源码探索
上面我们看到了连个函数,我们在源码中找这个连个函数是下面的样子:
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
我们会发现,两个函数中,都用到了AutoreleasePoolPage
,这两个函数就是对AutoreleasePoolPage
对应的静态方法push
&pop
的封装。
那么我们就再去寻找一下AutoreleasePoolPage
。
class AutoreleasePoolPage : private AutoreleasePoolPageData
{.......}
///继续跟进
👇👇
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
-
magic
用来校验AutoreleasePoolPage
的结构是否完整; -
next
指向最新添加的autoreleased
对象的下一个位置,初始化时指向begin()
; -
thread
指向当前线程; -
parent
指向父结点,第一个结点的parent
值为nil
; -
child
指向子结点,最后一个结点的child
值为nil
; -
depth
代表深度,从 0 开始,往后递增 1; -
hiwat
代表high water mark
最大入栈数量标记。(这个地方验证上面的猜想,自动释放池一定跟栈有关。)
每一个自动释放池都是由一系列AutoreleasePoolPage
组成的,并且每一个AutoreleasePoolPage
的大小都是4096字节
(16进制0x1000)
#define I386_PGBYTES 4096 /* bytes per 80386 page */
#define PAGE_SIZE I386_PGBYTES
-
双向链表
自动释放池中的AutoreleasePoolPage
是以双向链表的形式连接起来的,这一点我们通过AutoreleasePoolPage
里面的参数parent
&child
就可以知道。
-
AutoreleasePoolPageData
结构体的内存大小为56
字节:- 属性
magic
的类型是magic_t
结构体,所占内存大小为m[4]
---4*4 = 16字节
; - 属性
next(指针)
、thread(对象)
、parent(对象)
、child(对象)
均占8
字节,所以8*4 = 32字节
; - 属性
depth
、hiwat
类型为uint32_t
,实际类型为unsigned int
类型,均占4
字节,所以4*2 = 8字节
。
- 属性
-
自动释放池中的栈
-
POOL_BOUNDARY(哨兵对象)
在我们接着源码分析之前,先来了解一个感念:POOL_BOUNDARY(哨兵对象)
哨兵对象
只是nil
的别名:
# define POOL_BOUNDARY nil
在每个自动释放池初始化调用objc_autoreleasePoolPush
的时候,都会把一个POOL_BOUNDARY
Push到自动释放池的栈顶,并且返回这个POOL_BOUNDARY(哨兵对象)
。
而当方法objc_autoreleasePoolPop
被调用的时候,就会像自动释放池中的对象发送release
消息,直到第一个POOL_BOUNDARY
。
objc_autoreleasePoolPush
通过上面的分析,我们已经直到objc_autoreleasePoolPush
实际调用的就是push
方法,那么我们就进入push
方法里面去一探究竟。
static inline void *push()
{
id *dest;
///判断是否有pool
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
// 压栈一个POOL_BOUNDARY,哨兵压栈
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
autoreleaseFast
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
上面分成了三种不同的情况:
-
1
:有hotPage
,并且当前page
不满- 调用
page->add(obj)
方法,将对象添加至AutoreleasePoolPage
的栈中。
- 调用
-
2
:有hotPage
并且page
已满- 调用
autoreleaseFullOPage
初始化一个新的页; - 调用
page->add(obj)
方法,将对象添加至AutoreleasePoolPage
的栈中(这个在autoreleaseFullOPage
函数里面有)。
- 调用
-
3
:无hotPage
- 调用
autoreleaseNoPage
创建一个hotPage
; - 调用
page->add(obj)
方法,将对象添加到AutoreleasePoolPage
的栈中。
- 调用
通过上面可以看到,最后都会调用page->add(obj)
方法,将对象添加到自动释放池中。hotPage
可以理解为当前正在使用的AutoreleasePoolPage
。
page->add(obj)
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
这个方法其实就是一个压栈的操作,将对象加入AutoreleasePoolPage
,饭后移动栈顶指针。
autoreleaseFullOPage
(当前的hotPage
已满)
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
它会从传入的page
开始遍历整个双向链表,直到
1
:查找到一个未满的AutoreleasePoolPage
。
2
:使用构造器传入parent
创建一个新的AutoreleasePoolPage
在查找到一个可以使用的AutoreleasePoolPage
之后,会将该页面标记成houPage
,然后调用page->add(obj)
方法,添加对象。
-
autoreleaseNoPage
(没有hotPage
)
如果当前内存中不存在hotPage
的时候,就会调用autoreleaseNoPage
方法初始化一个AutoreleasePoolPage
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
ASSERT(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
objc_thread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder'd pool.
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
由于是从新构建的自动释放池的双向链表,所以新的AutoreleasePoolPage
没有parent
指针。
初始化之后,将当前页标记为hotPage
,然后先向这个page
中添加一个POOL_BOUNDARY(哨兵对象)
,确保在pop
的时候不会报错。
最后依然是page->add(obj)
,将对象添加到自动释放池。
objc_autoreleasePoolPop
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
可以看到objc_autoreleasePoolPop
方法的调用,是有一个参数传递进来的。那么这个参数是什么呢?不知道大家还记不记得我们上面clang
出来的main.cpp
文件。我们再来看一下里面的代码:
struct __AtAutoreleasePool {
///构造函数
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
///析构函数
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
大家发现没有,传入的参数就是push
压栈后返回的哨兵对象atautoreleasepoolobj
。
通过上面我们已经知道,objc_autoreleasePoolPop
最终调用的是pop
方法,那么我们就来看一下pop
方法:
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
// 判断token是否是空占位符
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
// 获取当前页
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
// 如果当前页不存在,则清楚空占位符
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
// 如果当前页存在,则将当前页设置为coldPage,token设置为coldPage的开始位置
page = coldPage();
token = page->begin();
} else {
// 获取token所在的页
page = pageForPointer(token);
}
stop = (id *)token;
// 判断最后一个位置,是否是哨兵
if (*stop != POOL_BOUNDARY) {
// 进入 if 说明最后一个位置不是哨兵,也就是说最后一个位置是一个对象
if (stop == page->begin() && !page->parent) {
// 如果是第一个位置,并且没有父结点,什么也不做
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// 出现混乱
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
// 出栈页
return popPage<false>(token, page, stop);
}
- 我们在
pop
方法中看到,正常情况下,最后出栈调用的是popPage
方法;那么我们再来追踪popPage
方法。
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
// 出栈当前操作页面对象
page->releaseUntil(stop);
// memory: delete empty children
// 删除空子项
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
// 特殊情况:debug期间,删除所有的池
// 获取当前页的父结点
AutoreleasePoolPage *parent = page->parent;
// 将当前页kill
page->kill();
// 设置操作页为父结点页
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
} else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
可以看到popPage
中,会通过releaseUntil
出栈当前页stop
位置之前的所有对象,即向栈中的对象发送release消息
,直到遇到传入的哨兵对象
。还有就是kill
掉child页
,这一步操作可能有什么其他的考虑,暂时不是很清楚。但是既然是出栈,那重点就是releaseUntil
;延续我们之前的思路,继续追踪releaseUntil
:
// 释放 stop 位置之前的所有对象
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
// 判断下一个对象是否是stop,如果不是继续循环
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
// 获取当前操作页,即hot页
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
// 如果当前页为空
while (page->empty()) {
// 将page的父结点页赋值个page
page = page->parent;
// 设置当前页为父结点页
setHotPage(page);
}
page->unprotect();
// page->next减减,出栈
id obj = *--page->next;
// 将page->next位置的索引,设置为SCRIBBLE,表示已经被释放
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
// 对象不是哨兵,就释放对象
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
通过源码我们开看到,raleaseUntil
主要是通过循环遍历
,判断当前对象是否是stop
,其目的是释放stop之前的所有对象
。
i
:首先通过page
的next
获得对象,对next
进行减减
操作,并且对索引进行更改;
ii
:判断获得的对象是否为哨兵对象
,如果不是,就释放对象。
- 在上面我们还提到了
kill
,同过字面意思也能理解这个方法是做什么的。
它会将当前页面以及子页面全部销毁。
不过我们还是再来看一期其内部实现:
void kill()
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
AutoreleasePoolPage *page = this;
// 一直循环到最后一页
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
探索了这么多,我们对于自动释放池
的本质、压栈、出栈都有了一定的了解。但是还有一个知识点我们还没探索到,那就是autorelease
。
autorelease
- 如果不是对象,或者是小对象,直接返回;
- 如果是对象,则调用对象的
autorelease
方法,进行释放。
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
跟进对象的autorelease
方法:
// objc_object::autorelease()inline id
objc_object::autorelease()
{
// 判断是否是 `Tagged Pointer`,这个函数并不希望处理的对象是`Tagged Pointer`
ASSERT(!isTaggedPointer());
// 通过 `hasCustomRR`,检查 类(包括其父类)中是否含有默认的方法
if (fastpath(!ISA()->hasCustomRR())) {
// 如果没有,调用`rootAutorelease`函数
return rootAutorelease();
}
// 如果有,则调用自定义方法
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
👇👇👇
// objc_object::rootAutorelease()
inline id
objc_object::rootAutorelease()
{
// 如果是小对象,直接返回
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
👇👇👇
// objc_object::rootAutorelease2()
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
👇👇👇
// AutoreleasePoolPage::autorelease((id)this)
static inline id autorelease(id obj)
{
ASSERT(obj);
ASSERT(!obj->isTaggedPointer());
// 压栈操作
id *dest __unused = autoreleaseFast(obj);
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
👇👇👇
// AutoreleasePoolPage::autoreleaseFast((id)this)
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
所以autorelease
的函数调用栈是这个样子的:
看到
autorelease
的函数调用栈之后,不知道大家有没有感到熟悉;没错,有很多方法,我们在上面探索objc_autoreleasePoolPush
的时候也见到过。那么我们将autorelease
和我们的压栈
结合起来,把整个流程串起来:自动释放池出栈流程图:
总结:
1、自动释放池
是由AutoreleasePoolPage
以双向链表的形式实现的。
2、当对象调用autorelease
方法的时候,会将对象加入AutoreleasePoolPage
的栈中
3、调用AutoreleasePoolPage::pop
方法会向栈中的对象发送release
消息。也就是我们所说的出栈,主要通过page-next
的递减
操作来完成,当出栈对象不是哨兵
的时候,释放对象。
-
Tips
-
assert
断言
我们在源码中发现有大量的断言使用,比如:ASSERT(!hotPage());
在源码中,我们看到它是一个宏,我们跟进去看看这个宏是什么样的:
-
// An assert that's disabled for release builds but still ensures the expression compiles.
#ifdef NDEBUG
#define ASSERT(x) (void)sizeof(!(x))
#else
#define ASSERT(x) assert(x)
#endif
*****************
我们看到官方注释的很清楚,这个断言不能用于发布模式。
assert
的作用是:校验传入的参数是否为真;如果为假,则向stderr
打印一条出错信息,然后通过abort
来终止应用程序。
assert
的缺点是:频繁的调用会极大的影响程序的性能,增加额外的开销。
用法和注意事项:
1、在函数开始处,检验传入参数的合法性int resetBufferSize(int newSize) { assert(newSize >= 0); assert(newSize <= MaxSize); }
2、每个
assert
只校验一个条件,因为同时校验多个条件时,如果断言失败,无法直观的判断是哪个条件失败。// 错误示范 assert(a > 0 && b > 0); // 正确示范 assert(a>0); assert(b>0);
3、不能使用改变环境的语句。
// 错误示范 如果在执行之前 `i==100` , 那么这条语句就不会执行,那么`i++`这条命令就没有执行。 assert(i++ < 100); // 正确示范 assert(i < 100); i++;
4、
assert
和后面的语句应空一行,以形成逻辑个视觉上的一致感。
-
Tagged Pointer
Tagged Pointer
是一个特别的指针,它分为两个部分:
i
:一部分直接保存数据;
ii
:一部分作为特殊标记,表示这个是一个特别的指针,不指向任何一个地方。
因此Tagged Pointer
也被叫做伪指针-
Tagged Pointer
被设计的目的是用来存储较小的对象,例如NSNumber
、NSDate
、NSString
等等。 -
Tagged Pointer
的值不再表示地址,而是真正的值。 -
Tagged Pointer
在内存读取上有这3倍的效率,创建的时候比以前快106倍。
-
常见面试题
-
面试题1:临时变量什么时候释放?
- 如果在正常情况下,一般是
超出作用域就会立即释放
。 - 如果将临时变量加入了
自动释放池
,会延迟释放,即在RunLoop休眠
或AutoreleasePool作用域之后
释放。
- 如果在正常情况下,一般是
-
面试题2:AutoreleasePool原理
- 自动释放池的本质是一个
AutoreleasePoolPage结构体对象
,是一个栈结构存储的页
,每一个AutoreleasePoolPage
都是以双向链表
的形式连接的。 - 自动释放池的
压栈
和出栈
主要是通过结构体的构造函数
和析构函数
调用底层的objc_autoreleasePoolPush
和objc_autoreleasePoolPop
,进而调用AutoreleasePoolPage
的push
和pop
两个方法。 - 每次调用
push
操作,其实就是创建一个新的AutoreleasePoolPage
,而AutoreleasePoolPage
的具体操作就是插入一个POOL_BOUNDARY(哨兵对象)
,并返回插入POOL_BOUNDARY
的内存地址。而push
内部调用autoreleaseFast
方法处理,主要有以下三种情况:- 当
page存在,且不满
的时候,调用add
方法将对象添加至page
的next
指针处,并将next
递增。 - 当
page存在,且已满
的时候,调用autoreleaseFullPage
初始化一个新的page
,然后调用add
方法将对象添加至page栈
中。 - 当
page不存在
的时候,调用autoreleaseNoPage
创建一个hotPage
,然后调用add
方法,将对象添加到page栈
中。
- 当
- 自动释放池的本质是一个
当执行
pop
操作的时候,会传入一个值,这个值就是push
操作的返回值,即POOL_BOUNDARY
的内存地址token
。所以pop
内部的实现就是根据token
找到哨兵对象
所处的page(页)
,然后使用objc_release
释放token
之前的所有对象,并把next
指针指向正确的位置。-
面试题3:AutoreleasePool能否嵌套使用?
- 可以嵌套使用,其目的是
控制应用程序的内存峰值
,使其不要太高。 - 可以嵌套使用的原因是因为:自动释放池是以栈为结点,通过双向链表的形式连接的,且是和线程一一对应的。
- 自动释放池的
多层嵌套
其实就是不停的push哨兵对象
,在pop
时,会先释放里面的,再释放外面的。
- 可以嵌套使用,其目的是
-
面试题4:哪些对象可以加入AutoreleasePool?alloc创建的可以吗?
- 使用
new / alloc / copy
关键字生成的对象 和retain
了的对象需要手动释放,不会被添加到自动释放池中。 - 设置为
autorelease
的对象不需要手动释放
,会直接进入自动释放池。 - 所有
autorelease
的对象,在出了作用域之后,会被自动添加到最新创建的自动释放池之中。
- 使用
-
面试题5:AutoreleasePool的释放时机是什么时候?
- APP启动之后,系统在主线程
RunLoop
里面注册了两个Observer
,其回调都是_wrapRunLoopWithAutoreleasePoolHander()
。 - 第一个
Observer
监听的事件是Entry(即将进入RunLoop)
,其回调内会调用_objc_autoreleasePoolPush()
创建自动释放池。其order
是-2147183647
(优先级最高),保证创建自动释放池发生在其他所有回调之前。 - 第二个
Observer
监听两个事件:
1、BeforWaiting(准备进入休眠)
,这个时候调用_objc_autoreleasePoolPop()
&_objc_autoreleasePoolPush()
,释放旧池并创建新池
2、Exit(即将推出Loop)
,这个时候调用_objc_autoreleasePoolPop()
释放自动释放池。这个Observer
的order
是2147483647
(优先级最低),保证其释放池子的操作发生在其他所有回调之后。
- APP启动之后,系统在主线程
-
面试题6:thread 和 AutoreleasePool的关系
- 每个线程(包括主线程在内),都维护了
自己的自动释放池堆栈结构
。 - 新的自动释放池在被创建的时候,会被添加到
栈顶
;当自动释放池销毁的时候,会从栈
中移除。 - 对于
当前线程
来说,会将自动释放池对象
放入自动释放池的栈顶
;在线程停止的时候,会自动释放掉与该线程关联的所有自动释放池
。
- 每个线程(包括主线程在内),都维护了
每个线程都有与之关联的自动释放池堆栈结构,新的
pool
在创建的时候回被压栈到栈顶;pool
销毁的时候,会被出栈。
对于当前线程来说,释放对象呗压栈到栈顶,线程停止时,会自动释放与之关联的自动释放池。
-
面试题7:RunLoop 和 AutoreleasePool的关系
- 主程序的
RunLoop
在每次事件循环
之前,会自动创建一个autoreleasepool
。 - 在
事件循环
结束的时候,执行drain
操作,释放其中的对象。
- 主程序的
参考资料
AutoReleasePool & NSRunLoop 底层分析
自动释放池的前世今生 ---- 深入解析 autoreleasepool
断言(assert)的用法
聊聊伪指针 Tagged Pointer