在iOS内存管理中,在ARC机制中,我们通常使用AutoreleasePool进行内存管理,本篇文章我们主要来分析autoreleasePool的原理。
分析
我们在开发中 使用 @autoreleasepool{}
方法将代码运行的对象,交给自动释放池管理,首先我们将以下代码转化为c++
函数
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%@",obj);
}
return 0;
}
经clang
编译后,我们可以看到autoreleasepool
的结构为如下结构体
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
我们可以看出,在__AtAutoreleasePool
的构造方法
中调用了objc_autoreleasePoolPush
方法,在析构方法
中调用了objc_autoreleasePoolPop
方法。
AutoreleasePoolPage
我们在源码中找到了objc_autoreleasePoolPush
的实现
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
·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)
{
}
};
它继承自AutoreleasePoolPageData
,是双向链表结构
,其大小为 504 * 8 + 56
,一页能存储 504个8字节 的对象
-
magic
: 用来校验AutoreleasePoolPage
的完整性。 -
next
: 指向 最新添加到AutoreleasePoolPage
的对象,初始化时指向begin
-
thread
: 与page
绑定的线程。 -
parent
: 指向上一页,第一页
的parent
指向nil
。 -
child
: 指向下一页,最后一页
的child
指向nil
。 -
depth
: 代表页的深度,每增加一页,depth 加 1。 -
hiwat
: 入栈对象的个数,即添加到该页中对象的数量。
最后一页为 hotPage
,其余页为coldPage
。
autoreleasePoolPush
在 push方法中
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
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);
}
}
在 autoreleaseFast
方法中,实现了向自动释放池添加对象的操作。
那它是如何向 page
中添加对象的呢?
1: 首先会根据当前thread
的key
,获取hotPage
。
2 : 如果没有hotPage
,说明还没有page
。
2.1:创建一个新的 page
。
2.2: 将当前page
设置为 hotPage
。
2.3: 添加一个哨兵对象(边界)。
2.4: 将对象添加到 hot页中。
3: 如果有hotPage
,会判断hotPage
页是否已满
3.1: 如果满了,会根据child
找到最后一页,然后新建一个 poolPage
,将新创建的页设置为 hotPage
,最后将对象添加到该页中。
3.2: 如果没满,直接将对象添加到hotPage
中。
哨兵对象(边界)只有在调用objc_autoreleasePoolPush
方法中才会添加。
整体过程如下图所示:
add
对象是怎样添加到 hotPage
中的呢?
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
通过next
指针,next
指向page页
最后一个对象,将新对象添加到最后一个对象的后面。
objc_autoreleasePoolPop
在自动释放池进行释放的时候会调用 objc_autoreleasePoolPop(poolObj)
方法。
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
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.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
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);
}
1,如果poolObj
不为空,在判断poolobj是不是begin且没有父页,那么该autoreleasepool已没有对象。
2,如果 *poolobjc为哨兵对象(边界),则执行 popPage
函数
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
AutoreleasePoolPage *parent = page->parent;
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();
}
}
}
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
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
}
我们来着重分析releaseUntil
函数,在releaseUntil
方法中
- 1,先找到
hotPage
。 - 2,操作
next
指针,对 obj 执行 objc_release操作,再偏移sizeof(obj)
大小找到下一个obj。 - 3,对当前页所有对象release完后,将其
parent
页设置为hotpage,重复该过程。
其过程如下图所示: