AutoreleasePool 深入浅出

238 阅读7分钟

AutoreleasePool 

Q: Autorelease对象什么时候释放? 当问到这个问题的时候,很多答案都是“当前作用域大括号结束时释放”,显然木有正确理解Autorelease机制。 在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop

APPKit 和 UIKit在Runloop 每次循环开启时,会隐式的创建一个autoreleasepool,并在每次循环结束时销毁, 在销毁时释放掉里面的所有autorelease对象. 在ARC中, 已弃用NSAutoreleasePool类创建自动是否池, 只能使用@autoreleasepool 来创建. 苹果官方提示 @autoreleasepool要比NSAutoreleasepool快6倍

autoreleasepool底层 


// autoreleasepool 底层为一个结构体 对象

struct __AtAutoreleasePool{

    // 构造函数 内部调用 objc_autoreleasepool push 这里基本可以看出 autoreleasepool应该是栈结构

    __AtAutoreleasePool(){
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }

    // 析构函数 调用objc_autoreleasePool pop 出栈

    ~__AtAutoreleasePool(){
        objc_autoreleasePoolPop(autoreleasepoolobj);
    }

    void * autoreleasepoolobj;

}

@autoreleasepool底层是创建了一个__AtAutoreleasePool结构体对象;

在创建__AtAutoreleasePool结构体时会在构造函数中调用objc_autoreleasePoolPush()函数,并返回一个atautoreleasepoolobj(POOL_BOUNDARY存放的内存地址,下面会讲到);

在释放__AtAutoreleasePool结构体时会在析构函数中调用objc_autoreleasePoolPop()函数,并将atautoreleasepoolobj传入。

其中objc_autoreleasePoolPush()和objc_autoreleasePoolPop()都定义在AutoreleasePoolPage类中.


class AutoreleasePoolPage 

{

#   define EMPTY_POOL_PLACEHOLDER ((id*)1)  // EMPTY_POOL_PLACEHOLDER:表示一个空自动释放池的占位符

#   define POOL_BOUNDARY nil                // POOL_BOUNDARY:哨兵对象

    static pthread_key_t const key = AUTORELEASE_POOL_KEY;

    static uint8_t const SCRIBBLE = 0xA3;   // 用来标记已释放的对象

    static size_t const SIZE =              // 每个 Page 对象占用 4096 个字节内存

#if PROTECT_AUTORELEASEPOOL                 // PAGE_MAX_SIZE = 4096

        PAGE_MAX_SIZE;  // must be muliple of vm page size

#else

        PAGE_MAX_SIZE;  // size and alignment, power of 2

#endif

    static size_t const COUNT = SIZE / sizeof(id);  // Page 的个数



    magic_t const magic;                // 用来校验 Page 的结构是否完整

    id *next;                           // 指向下一个可存放 autorelease 对象地址的位置,初始化指向 begin()

    pthread_t const thread;             // 指向当前线程

    AutoreleasePoolPage * const parent; // 指向父结点,首结点的 parent 为 nil

    AutoreleasePoolPage *child;         // 指向子结点,尾结点的 child  为 nil

    uint32_t const depth;               // Page 的深度,从 0 开始递增

    uint32_t hiwat;

    ......

}


从AutoreleasePoolPage结构可以发现:

    * 自动释放池即所有的AutoreleasePoolPage对象是已栈为节点通过双向链表的结构组合而成的

    * 自动释放池与线程(即Runloop)一一对应

    * 每个 AutoreleasePoolPage对象 占用4096个字节空间, 其中56个字节用来存放自身成员变量,剩下的空间用来存放Autorelease对象的地址

POOL_BOUNDARY  哨兵

    * POOL_BOUNDARY用来区分不同的释放池,以解决自动释放池嵌套问题;

    * 每当创建一个自动释放池,就会调用push()方法,将一个POOL_BOUNDARY入栈,并返回其内存地址

    * 当往自动释放池中添加 Autorelease对象时,将Autorelease对象地址入栈, 他们前面至少有一个POOL_BOUNDARY

    * 当自动释放池销毁时, 会调用pop()方法并传入一个POOL_BOUNDARY,会从自动释放池中最后一个对象开始,依次给他们发送release消息,直到遇到POOL_BOUNDARY.

push 方法


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);  // 传入 POOL_BOUNDARY 哨兵对象

        }

        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);

        return dest;

    }


autoreleaseFast(id obj) 方法


static inline id *autoreleaseFast(id obj)

    {

        AutoreleasePoolPage *page = hotPage();     // 双向链表中的最后一个 Page

        if (page && !page->full()) {        // 如果当前 Page 存在且未满

            return page->add(obj);                 // 将 autorelease 对象入栈,即添加到当前 Page 中;

        } else if (page) {                  // 如果当前 Page 存在但已满

            return autoreleaseFullPage(obj, page); // 创建一个新的 Page,并将 autorelease 对象添加进去

        } else {                            // 如果当前 Page 不存在,即还没创建过 Page

            return autoreleaseNoPage(obj);         // 创建第一个 Page,并将 autorelease 对象添加进去

        }

    }

快速添加autorelease对象的三种情况

    * 当当前page(hotPage, autoreleasePoolPage池的最后一个对象)存在并且未满时, 直接添加到当前page ,通过page->add(obj) 将obj对象入栈

    * 当当前page存在但已满时, 通过autoreleaseFullPage(obj, page)将autorelease对象(obj)和当前page传入,创建一个新的page,并将obj对象添加进新的page

    * 当前page不存在,即自动释放池里面还没有创建过page时, 通过autoreleaseNoPage(obj)方法创建第一个page,并将obj对象添加进去

page->add() 方法


// 其实就是将 autorelease对象添加到next指针所指向的地址,并将next指针指向这个对象的下一个位置的地址, 然后将该位置返回

id *add(id obj)

{

    assert(!full());

    unprotect();

    id *ret = next;  // faster than `return next-1` because of aliasing

    *next++ = obj;

    protect();

    return ret;

}

autoreleaseFullPage(id obj, AutoreleasePoolPage *page)  方法


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);

   }

解析:  

    * 开启一个do while 循环, 先判断当前page的是否有子page(child是否存在), 如果存在将page指向child, 如果不存在就创建一个新的page, 并赋值给page 判断page是否已满, 未满则退出循环

    * 将page设置成当前page hotPage()

    * 将obj添加到page里面 并返回

autoreleaseNoPage 方法


static __attribute__((noinline))

    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", 

                         pthread_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);

    }

autoreleaseNoPage()方法中会创建第一个Page。该方法会判断是否有空的自动释放池存在,如果没有会通过setEmptyPoolPlaceholder()生成一个占位符,表示一个空的自动释放池。接着创建第一个Page,设置它为hotPage。最后将一个POOL_BOUNDARY添加进Page中,并返回POOL_BOUNDARY的下一个位置。

autorelease 

autorelease 调用栈: 

    objc_autorelease -> objc_object::autorelease -> autorelease -> _objc_rootAutorelease -> objc_object::rootAutorelease2 -> AutoreleasePoolPage::autorelease 

AutoreleasePoolPage::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()方法添加进page里面的 这样就印证了上面的说法

pop() 方法


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();

            }

        }

    }

pop方法传token即为POOLBOUND对于在page中的地址, 当销毁自动释放池时, 会调用pop( )方法将自动释放池中的autorelease对象按照出栈的顺序,从page的最后一个对象开始依次给他们发送一条release消息,直到遇到这个page的POOL_BOUNDARY. 

    

    * 判断token是不是 EMPTY_POOL_PLACEHOLDER,是的话就清空这个自动释放池

    * token不是EMPTY_POOL_PLACEHOLDER的话, 就通过pageGetPointer(token)拿到token所在的page

    * 通过page->releaseUntil(stop)方法将page中的autorelease对象全部释放, stop即为page的POOL_BOUNDARY的地址

    * 判断当前page是否有child (page->child), 有的话销毁child

releaseUntil(id *stop) 方法


void releaseUntil(id *stop) 

    {

       

       // 循环条件 当page的next指针指向stop的时候 退出循环

        while (this->next != stop) {

            

            // 从hotPage 当前page开始释放 

            AutoreleasePoolPage *page = hotPage();


            // 当前page为空的时候 将page指向父page 将父page设置成当前page 

            while (page->empty()) {

                page = page->parent;

                setHotPage(page);

            }

            
            page->unprotect();

            // next指针是指向最后一个对象的后一个位置,所以需要先减1

            id obj = *--page->next;  

            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));

            page->protect();

            

            // 如果obj对象不是POOL_BOUNDARY 释放obj

            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方法其实就是通过一个while循环,从最后一个入栈的autorelease对象开始,依次给它们发送一条release消息,直到遇到这个POOL_BOUNDARY。

引用:# iOS - 聊聊 autorelease 和 @autoreleasepool