美文网首页
AutoreleasePool

AutoreleasePool

作者: Bel李玉 | 来源:发表于2020-12-07 23:06 被阅读0次

    在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字节 的对象

    AutoreleasePoolPage.png
    • magic: 用来校验AutoreleasePoolPage的完整性。
    • next: 指向 最新添加到 AutoreleasePoolPage的对象,初始化时指向 begin
    • thread: 与 page绑定的线程。
    • parent: 指向上一页,第一页parent指向nil
    • child: 指向下一页,最后一页child指向nil
    • depth: 代表页的深度,每增加一页,depth 加 1。
    • hiwat: 入栈对象的个数,即添加到该页中对象的数量。

    最后一页为 hotPage,其余页为coldPage

    AutoreleasePoolPageData联系.png

    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: 首先会根据当前threadkey,获取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方法中才会添加。

    整体过程如下图所示:


    autoreleasePoolPush.png

    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,重复该过程。

    其过程如下图所示:


    objc_autoreleasePoolPop.png

    相关文章

      网友评论

          本文标题:AutoreleasePool

          本文链接:https://www.haomeiwen.com/subject/wzmawktx.html