虽然现在已经是arc的时代了,不用我们自己管理对象的释放问题,但在面试的过程中难免会被问到这方面的问题,另一方了解AutoreleasePool的实现原理也会使我们对内存管理这方面有更加清晰的认识。
再看这篇文章之前,先回到我们的题目中来,假如你现在正在面试,面试官问你,说说你对AutoreleasePool的了解。。。
AutoreleasePool就是我们常说的自动释放池,在mrc的时候采用引用计数器来管理对象,new,retain,使对象的引用计数器+1,release使引用计数器-1,当对象的引用计数器为0时,会把对象放在AutoreleasePool中,当runloop将要休眠的时候,会对AutoreleasePool中的对象发消息,进行回收。。。不知道有没有人会这样回答,很明显这不是面试官想要听到的回答,于是便有了下面的这篇文章。
本文中用到的runtime源码是objc4-750.tar.gz
首先我们找到NSObject.mm文件,从577行开始描述AutoreleasePool
The stack is divided into a doubly-linked list of pages.
注释中说道,内部的实现其实是一个双向链表,再往下看我们发现了AutoreleasePoolPage
# define POOL_BOUNDARY nil
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool empty() {
return next == begin();
}
bool full() {
return next == end();
}
bool lessThanHalfFull() {
return (next - begin() < (end() - begin()) / 2);
}
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
在add方法中我们发现next指向的是刚添加进来对象的下一个位置
继续往下看,我们发现了autorelease方法,大家是不是很眼熟呢?
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;
}
在autorelease方法中调用了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);
}
}
在autoreleaseFast中通过key获取到一个page,判断当前的page是否满了,没满,直接调用add方法,满了的话,判断是否有child节点,有的话,page=page->child,没有的话,创建一个,并将当前page设置为hotPage。大致的调用顺序如下,
紧接着是push和pop方法
static inline void *push()
{
id *dest;
if (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;
}
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
push的大致过程和autorelease相似,这里不作过多的赘述,需要注意的是push方法返回值是next
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
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 (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (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();
}
}
}
首先判断传入的token指针和EMPTY_POOL_PLACEHOLDER
是否相等,做了简单的处理,紧接着调用了pageForPointer
方法
static AutoreleasePoolPage *pageForPointer(const void *p)
{
return pageForPointer((uintptr_t)p);
}
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
uintptr_t offset = p % SIZE;
assert(offset >= sizeof(AutoreleasePoolPage));
result = (AutoreleasePoolPage *)(p - offset);
result->fastcheck();
return result;
}
通过传入的指针经过计算返回一个AutoreleasePoolPage类型的对象。
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
}
这个方法中while循环条件比较this.next 是否和传入的stop指针地址相同,从hotPage开始依次取出待释放的对象发送release
消息,如果当前page对象都释放,继续向前驱节点释放,直到this.next == stop
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);
}
kill()
这个方法的作用是把当前节点及后面的节点全部删除掉。
ARC下,我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将其改写成下面的样子:
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
然后我们可以在1831行发现这几个方法
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
void *
_objc_autoreleasePoolPush(void)
{
return objc_autoreleasePoolPush();
}
void
_objc_autoreleasePoolPop(void *ctxt)
{
objc_autoreleasePoolPop(ctxt);
}
至此,是不是有那么一丢丢明白了它的大致实现流程:
- AutoreleasePool的底层实现是一个以
AutoreleasePoolPage
为节点的双向链表结构。 - 在
@autoreleasepool{}
方法中系统默认的为我们调用了底层的push
方法,将需要释放的对象添加到page节点的栈中,并且返回当前page的next指针。 - 将
push
方法返回的next
指针作为参数调用底层的pop
方法,对next之前的待释放对象一次发送release消息进行释放,最后删除空的page及page->child.。
参考