AutoreleasePool探究

虽然现在已经是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;

AutoreleasePoolPage.png
    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。大致的调用顺序如下,

autorelease.png

紧接着是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.。

参考

你可能感兴趣的:(AutoreleasePool探究)