美文网首页程序员
浅析AutoreleasePool源码

浅析AutoreleasePool源码

作者: 莫云溪 | 来源:发表于2018-04-14 15:27 被阅读0次

    最近在拜读Draveness大佬的一篇文章自动释放池的前世今生 ---- 深入解析 autoreleasepool,看到文中给读者留了一个问题:

    我到现在也不是很清楚为什么要根据当前页的不同状态 kill 掉不同 child 的页面。

    关于AutoreleasePool是什么,强力推荐阅读原文,写的很好。这里就不说了,直接讨论问题。

    首先是整个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 fully
                if (page->lessThanHalfFull()) {
                    page->child->kill();
                }
                else if (page->child->child) {
                    page->child->child->kill();
                }
            }
        }
    

    我们先看看释放的函数releaseUntil,它在释放的时候其实会一直顺着parent往前释放,直到参数stop,也就是说可能一次性释放好几个page

    // 代码有所删减
    void releaseUntil(id *stop)
    {
        while (this->next != stop) {
            AutoreleasePoolPage *page = hotPage();
            
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }
            
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            
            if (obj != POOL_BOUNDARY) {
                objc_release(obj);
            }
        }
        
        setHotPage(this);
    }
    

    然后我们来看看这段有疑问的代码

            // memory: delete empty children
            if (DebugPoolAllocation  &&  page->empty()) {  // 分支1
                // special case: delete everything during page-per-pool debugging
                AutoreleasePoolPage *parent = page->parent;
                page->kill();
                setHotPage(parent);
            } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) { // 分支2
                // special case: delete everything for pop(top) 
                // when debugging missing autorelease pools
                page->kill();
                setHotPage(nil);
            } 
            else if (page->child) { // 分支3
                // hysteresis: keep one empty child if page is more than half fully
                if (page->lessThanHalfFull()) {
                    page->child->kill();
                }
                else if (page->child->child) {
                    page->child->child->kill();
                }
            }
    

    这块代码的作用是删除空的子节点,释放内存。pop之后三种情况:

    1. 当前page为空,直接kill掉当前page,然后把parent设置为hotpage
    2. 当前page为空,而且没有parent,kill掉当前pagehotpage置为空;
    3. 当前page不为空,但是有child,如果当前page的空间占用不到一半,释放child,如果当前page的空间占用超过一半,且child还有child,直接释放这个孙子辈的page。(对于第三步注释中的解释是:keep one empty child if page is more than half fully)

    我们再看看kill的实现,可以发现他是会顺着child一直往后释放,保证释放节点的child page都被释放了。

    void kill()
    {
        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);
    }
    

    到这里就可以得出结论了:

    1. pop之后,所有child page肯定都为空了,且当前page一定是hotPage
    2. 系统为了节约内存,判断,如果当前page空间使用少于一半,就释放掉所有的child page,如果当前page空间使用大于一半,就从孙子page开始释放,预留一个child page

    欢迎关注我的博客

    相关文章

      网友评论

        本文标题:浅析AutoreleasePool源码

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