ios內(nèi)存管理(四):Autorelease技術(shù)

??前面章節(jié)提到內(nèi)存釋放時(shí),經(jīng)常會(huì)說(shuō)到當(dāng)超出變量作用域時(shí),變量會(huì)被“自動(dòng)”釋放,其實(shí)這只是為了更加簡(jiǎn)單的說(shuō)明這個(gè)過(guò)程。實(shí)際上,在ARC模式下是系統(tǒng)幫你自動(dòng)插入了相應(yīng)的release邏輯。這個(gè)流程有沒(méi)有什么問(wèn)題呢?
??請(qǐng)看如下代碼:

-(NSObject *)objFactory() {
    NSObject *obj = [[NSObject alloc];
    return obj;
}

??這個(gè)函數(shù)生成了一個(gè)NSObject對(duì)象,并作為了返回值。那么在超出函數(shù)作用域前,要不要插入[obj release]呢?怎么樣都不合適,如果插入,該函數(shù)的caller若用weak指針持有函數(shù)返回值,馬上會(huì)將其置為nil;如果不插入,caller可能不知道要release這個(gè)對(duì)象,又會(huì)造成內(nèi)存泄露。

??為了解決這個(gè)問(wèn)題,引入了一個(gè)非常重要的技術(shù),就是Autorelease。簡(jiǎn)單來(lái)講,就是用autorelease來(lái)代替release,將要釋放的對(duì)象先放入一個(gè)“釋放池”,而不是馬上釋放。這個(gè)自動(dòng)釋放池,就是AutoreleasePool。

??AutoreleasePool是OC中的一種內(nèi)存自動(dòng)回收機(jī)制,它可以延遲加入AutoreleasePool中的變量release的時(shí)機(jī)。在正常情況下,創(chuàng)建的變量會(huì)在超出其作用域的時(shí)候release,但是如果將變量加入AutoreleasePool,那么release將延遲執(zhí)行。

??AutoreleasePool是如何實(shí)現(xiàn)的呢?一個(gè)線程對(duì)應(yīng)有一個(gè)Autoreleasepool,其結(jié)構(gòu)是一個(gè)指針棧。棧中存放的指針指向加入需要release的對(duì)象或者POOL_SENTINEL(哨兵對(duì)象,用于分隔Autoreleasepool)。棧中指向POOL_SENTINEL的指針就是Autoreleasepool的一個(gè)標(biāo)記。當(dāng)Autoreleasepool進(jìn)行出棧操作,每一個(gè)比這個(gè)哨兵對(duì)象后進(jìn)棧的對(duì)象都會(huì)release。這個(gè)棧是由一個(gè)以page為節(jié)點(diǎn)雙向鏈表組成,page根據(jù)需求進(jìn)行增減。Autoreleasepool對(duì)應(yīng)的線程存儲(chǔ)了指向最新page(也就是最新添加autorelease對(duì)象的page)的指針。

??AutoreleasePoolPage的結(jié)構(gòu)如下:

    magic_t const magic; // 魔數(shù),校驗(yàn)用
    id *next; // 棧頂指針
    pthread_t const thread; // 當(dāng)前線程
    AutoreleasePoolPage * const parent; // 父節(jié)點(diǎn)
    AutoreleasePoolPage *child; // 子節(jié)點(diǎn)
    uint32_t const depth; // 鏈表節(jié)點(diǎn)的個(gè)數(shù)
    uint32_t hiwat; // high water mark(最高水位標(biāo)記)

??下面來(lái)看AutoreleasePool的幾個(gè)關(guān)鍵函數(shù)實(shí)現(xiàn):

   static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {    // 區(qū)別調(diào)試模式
            // Each autorelease pool starts on a new pool page.
            // 調(diào)試模式下將新建一個(gè)鏈表節(jié)點(diǎn),并將一個(gè)哨兵對(duì)象添加到鏈表?xiàng)V?            dest = autoreleaseNewPage(POOL_SENTINEL);
        } else {
            dest = autoreleaseFast(POOL_SENTINEL);    // 添加一個(gè)哨兵對(duì)象到自動(dòng)釋放池的鏈表?xiàng)V?        }
        assert(*dest == POOL_SENTINEL);
        return dest;
    }
    static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();    // 獲取最新的page(即鏈表上最新的節(jié)點(diǎn))
        if (page && !page->full()) {
            return page->add(obj);    // 在這個(gè)page存在且不滿的情況下,直接將需要autorelease的對(duì)象加入棧中
        } else if (page) {
            return autoreleaseFullPage(obj, page);    // 在這個(gè)page已經(jīng)滿了的情況下,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
        } else {
            return autoreleaseNoPage(obj);    // 在沒(méi)有page的情況下,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
        }
    }
    id *add(id obj)   // 入棧操作
    {
        assert(!full());
        unprotect();    // 解除保護(hù)
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;    // 將obj入棧到棧頂并重新定位棧頂
        protect();    // 添加保護(hù)
        return ret;
    }

    static inline void pop(void *token)   // token指針指向棧頂?shù)牡刂?    {
        AutoreleasePoolPage *page;
        id *stop;

        page = pageForPointer(token);   // 通過(guò)棧頂?shù)牡刂氛业綄?duì)應(yīng)的page
        stop = (id *)token;
        if (DebugPoolAllocation  &&  *stop != POOL_SENTINEL) {
            // This check is not valid with DebugPoolAllocation off
            // after an autorelease with a pool page but no pool in place.
            _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", 
                        token);
        }

        if (PrintPoolHiwat) printHiwat();   // 記錄最高水位標(biāo)記

        page->releaseUntil(stop);   // 從棧頂開(kāi)始操作出棧,并向棧中的對(duì)象發(fā)送release消息,直到遇到第一個(gè)哨兵對(duì)象

        // memory: delete empty children
        // 刪除空掉的節(jié)點(diǎn)
        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();
            }
        }
    }
    static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);   //  添加obj對(duì)象到自動(dòng)釋放池的鏈表?xiàng)V?        assert(!dest  ||  *dest == obj);
        return obj;
    }

??加入AutoreleasePool的對(duì)象,會(huì)在[pool drain]的時(shí)候release。那么drain方法到底在什么時(shí)候調(diào)用呢?這里又引出了一個(gè)Runloop的概念。對(duì)于每一個(gè)Runloop, 系統(tǒng)會(huì)隱式創(chuàng)建一個(gè)Autorelease pool,這樣所有的release pool會(huì)構(gòu)成一個(gè)象CallStack一樣的一個(gè)棧式結(jié)構(gòu),在每一個(gè)Runloop結(jié)束時(shí),當(dāng)前棧頂?shù)腁utorelease pool會(huì)被銷(xiāo)毀,這樣這個(gè)pool里的每個(gè)Object會(huì)被release。那什么是一個(gè)Runloop呢? 一個(gè)UI事件,Timer call, delegate call, 都會(huì)是一個(gè)新的Runloop。
??下一章,再詳細(xì)研究Runloop到底是什么。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容