iOS自動(dòng)釋放池--autoreleasepool

在上一篇文章中国觉,詳細(xì)分析了IOS內(nèi)存管理的內(nèi)存布局吧恃、內(nèi)存管理方案、引用計(jì)數(shù)等內(nèi)容麻诀,本篇文章將繼續(xù)上篇文章的內(nèi)容探索自動(dòng)釋放池autoreleasepool的相關(guān)知識(shí)痕寓。

1、autoreleasepool初探

熟悉OC開發(fā)的都知道蝇闭,在main函數(shù)中就有@autoreleasepool這樣一個(gè)東西呻率,其實(shí)這就是自動(dòng)釋放池。那么@autoreleasepool的底層實(shí)現(xiàn)是什么樣的呢呻引?我們?cè)诿钚兄惺褂?clang -rewrite-objc main.m -o main.cpp 讓編譯器重新改寫這個(gè)文件礼仗,講得到一個(gè)main.cpp文件,打開該文件苞七,找到其中的main函數(shù)藐守。

int main(int argc, const char *argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    }
    return 0;
}

我們可以看到@autoreleasepool轉(zhuǎn)化成了__AtAutoreleasePool這樣一個(gè)結(jié)構(gòu)體,那么意味著@autoreleasepool的本質(zhì)就是__AtAutoreleasePool結(jié)構(gòu)體蹂风。

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

這個(gè)結(jié)構(gòu)體會(huì)在初始化時(shí)調(diào)用objc_autoreleasePoolPush() 方法卢厂,會(huì)在析構(gòu)時(shí)調(diào)用
objc_autoreleasePoolPop 方法。

這就說(shuō)明了main函數(shù)在實(shí)際工作的時(shí)候是這樣的:

int main(int argc, const char *argv[])
{
    void * atautoreleasepoolobj = objc_autoreleasePoolPush();

    // do whatever you want

    objc_autoreleasePoolPop(atautoreleasepoolobj);
    return 0;
}

似乎一切都是圍繞著objc_autoreleasePoolPush()objc_autoreleasePoolPop這兩個(gè)方法展開惠啄。那么我們來(lái)看下這兩個(gè)方法的源碼實(shí)現(xiàn):

void *
objc_autoreleasePoolPush(void)
{
    // 調(diào)用了AutoreleasePoolPage中的push方法
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    // 調(diào)用了AutoreleasePoolPage中的pop方法
    AutoreleasePoolPage::pop(ctxt);
}

上面的兩個(gè)方法看上去是對(duì)AutoreleasePoolPage對(duì)應(yīng)靜態(tài)方法pushpop的封裝慎恒。

2、AutoreleasePoolPage

在runtime中的源碼(objc4-756.2版本)中找到了一段注釋撵渡,這段注釋對(duì)我們理解AutoreleasePoolPage的底層結(jié)構(gòu)會(huì)有所幫助融柬。

  • A thread's autorelease pool is a stack of pointers.
  • Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
  • A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
  • The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.
  • Thread-local storage points to the hot page, where newly autoreleased objects are stored.

翻譯中文如下:

  • 一個(gè)線程的自動(dòng)釋放池是一個(gè)指針的堆棧結(jié)構(gòu)。
  • 每個(gè)指針代表一個(gè)需要釋放的對(duì)象或者POOL_BOUNDARY(自動(dòng)釋放池邊界)
  • 一個(gè) pool token 就是這個(gè) pool 所對(duì)應(yīng)的 POOL_BOUNDARY 的內(nèi)存地址趋距。當(dāng)這個(gè) pool 被 pop 的時(shí)候粒氧,所有內(nèi)存地址在 pool token 之后的對(duì)象都會(huì)被 release。
  • 這個(gè)堆棧被劃分成了一個(gè)以 page 為結(jié)點(diǎn)的雙向鏈表节腐。pages 會(huì)在必要的時(shí)候動(dòng)態(tài)地增加或刪除外盯。
  • Thread-local storage(線程局部存儲(chǔ))指向 hot page 摘盆,即最新添加的 autoreleased 對(duì)象所在的那個(gè) page 。

從上面這段注釋中我們可以知道自動(dòng)釋放池是一種棧的結(jié)構(gòu)饱苟,遵循先進(jìn)后出的原則孩擂,每一個(gè)自動(dòng)釋放池都是由一系列的AutoreleasePoolPage組成的,而AutoreleasePoolPage是以雙向鏈表的形式連接起來(lái)箱熬。

image

2.1类垦、AutoreleasePoolPage結(jié)構(gòu)

來(lái)看一下AutoreleasePoolPage的代碼定義(只列出了關(guān)鍵代碼,部分代碼省略)城须。

class AutoreleasePoolPage 
{
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)
#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    // AutoreleasePoolPage的大小蚤认,通過(guò)宏定義,可以看到是4096字節(jié)
    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;//16字節(jié)
    id *next;//8字節(jié)
    pthread_t const thread;//8字節(jié)
    AutoreleasePoolPage * const parent;//8字節(jié)
    AutoreleasePoolPage *child;//8字節(jié)
    uint32_t const depth;//4字節(jié)
    uint32_t hiwat;//4字節(jié)
}
  • magic:用來(lái)校驗(yàn)AutoreleasePoolPage的結(jié)構(gòu)是否完整酿傍。
  • *next:next指向的是下一個(gè)AutoreleasePoolPage中下一個(gè)為空的內(nèi)存地址(新來(lái)的對(duì)象會(huì)存儲(chǔ)到next處)烙懦,初始化時(shí)指向begin()
  • thread:保存了當(dāng)前頁(yè)所在的線程(一個(gè)AutoreleasePoolPage屬于一個(gè)線程赤炒,一個(gè)線程中可以有多個(gè)AutoreleasePoolPage)氯析。
  • *parent:指向父節(jié)點(diǎn),第一個(gè)parent節(jié)點(diǎn)為nil莺褒。
  • *child:指向子節(jié)點(diǎn)掩缓,最后一個(gè)child節(jié)點(diǎn)為nil
  • depth:代表深度遵岩,從0開始你辣,遞增+1。
  • hiwat:代表high water Mark最大入棧數(shù)尘执。
  • SIZEAutoreleasePoolPage的大小舍哄,值為PAGE_MAX_SIZE,4096個(gè)字節(jié)。
  • POOL_BOUNDARY:只是nil的別名誊锭。在每個(gè)自動(dòng)釋放池初始化調(diào)用 objc_autoreleasePoolPush 的時(shí)候表悬,都會(huì)把一個(gè)POOL_SENTINEL push到自動(dòng)釋放池的棧頂,并且返回這個(gè)POOL_SENTINEL自動(dòng)釋放池邊界丧靡。而當(dāng)方法 objc_autoreleasePoolPop調(diào)用時(shí)蟆沫,就會(huì)向自動(dòng)釋放池中的對(duì)象發(fā)送release消息,直到第一個(gè) POOL_SENTINEL温治。

AutoreleasePoolPage中的第一個(gè)對(duì)象是存儲(chǔ)在next后面饭庞,那么就形成如下圖所示這樣一個(gè)結(jié)構(gòu)。

image

其中的56個(gè)字節(jié)存儲(chǔ)的AutoreleasePoolPage的成員變量熬荆,其他的區(qū)域存儲(chǔ)加載到自動(dòng)釋放池的對(duì)象舟山。
當(dāng)next==begin()時(shí)表示AutoreleasePoolPage為空,當(dāng)next==end()的時(shí)表示AutoreleasePoolPage已滿。

2.2捏顺、AutoreleasePoolPage容量

在上一個(gè)小節(jié)的內(nèi)容中我們分析了AutoreleasePoolPage的結(jié)構(gòu)六孵,了解到每一個(gè)AutoreleasePoolPage的大小是4096字節(jié),其中56字節(jié)用于存儲(chǔ)成員變量幅骄,剩下的區(qū)域存儲(chǔ)加載到自動(dòng)釋放池的對(duì)象,那么似乎答案呼之欲出本今,一個(gè)AutoreleasePoolPage可以存儲(chǔ)(4096-56)/8=505個(gè)對(duì)象拆座。但是有一個(gè)注意的點(diǎn),第一個(gè)page存放的需要釋放的對(duì)象的容量應(yīng)該是504個(gè)冠息,因?yàn)樵趧?chuàng)建page的時(shí)候會(huì)在next的位置插入1POOL_SENTINEL挪凑。

image

2.3、push方法

通過(guò)前面小節(jié)的分析逛艰,我們知道objc_autoreleasePoolPush的本質(zhì)就是調(diào)用push方法躏碳。我們先來(lái)看下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;
}

push方法中實(shí)際上調(diào)用的是autoreleaseFast方法散怖,并且首先將一個(gè)POOL_BOUNDARY對(duì)象插入到棧頂菇绵。slowpath表示小概率發(fā)生。

2.3.1镇眷、autoreleaseFast方法

如下是autoreleaseFast方法的源碼

 static inline id *autoreleaseFast(id obj)
{
    // hotPage就是當(dāng)前正在使用的AutoreleasePoolPage
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {
        // 有hotPage且hotPage不滿咬最,將對(duì)象添加到hotPage中
        return page->add(obj);
    } else if (page) {
        // 有hotPage但是hotPage已滿
        // 使用autoreleaseFullPage初始化一個(gè)新頁(yè),并將對(duì)象添加到新的AutoreleasePoolPage中
        return autoreleaseFullPage(obj, page);
    } else {
        // 無(wú)hotPage
        // 使用autoreleaseNoPage創(chuàng)建一個(gè)hotPage,并將對(duì)象添加到新創(chuàng)建的page中
        return autoreleaseNoPage(obj);
    }
}

autoreleaseFast方法的代碼很簡(jiǎn)單欠动,只要是三個(gè)判斷分支永乌。

  1. 如果有hotPage且沒有滿,則調(diào)用add方法將對(duì)象添加到hotPage中具伍。否則執(zhí)行第2步翅雏。
  2. 如果有hotPage但是已經(jīng)滿了,則調(diào)用autoreleaseFullPage方法初始化一個(gè)新頁(yè)人芽,并將對(duì)象添加到新的AutoreleasePoolPage中望几。否則執(zhí)行第3步。
  3. 如果沒有hotPage啼肩,則調(diào)用autoreleaseNoPage方法創(chuàng)建一個(gè)hotPage,并將對(duì)象添加到新創(chuàng)建的page
hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage橄妆。

2.3.2、add 添加對(duì)象

add方法將對(duì)象添加到AutoreleasePoolPage中祈坠。

id *add(id obj)
{
    ASSERT(!full());
    unprotect();
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;//將obj存放在next處害碾,并將next指向下一個(gè)位置
    protect();
    return ret;
}

這個(gè)方法其實(shí)就是一個(gè)壓棧操作,將對(duì)象添加到AutoreleasePoolPage中赦拘,然后移動(dòng)棧頂指針慌随。

2.3.3、autoreleaseFullPage

autoreleaseFullPage方法會(huì)重新開辟一個(gè)新的AutoreleasePoolPage頁(yè),并將對(duì)象添加到其中阁猜。

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 {
        // 如果page->child不為空丸逸,那么使用page->child
        if (page->child) page = page->child;
        // 否則的話,初始化一個(gè)新的AutoreleasePoolPage
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    // 將找到的合適的page設(shè)置成hotPage
    setHotPage(page);
    // 將對(duì)象添加到hotPage中
    return page->add(obj);
}

遍歷找到未滿的的page剃袍,如果沒有找到則初始化一個(gè)新的page黄刚,并將page設(shè)置為hotPage,同時(shí)將對(duì)象添加到這個(gè)page中民效。

2.3.4憔维、autoreleaseNoPage

如果當(dāng)前內(nèi)存中不存在hotPage,就會(huì)調(diào)用autoreleaseNoPage方法初始化一個(gè)AutoreleasePoolPage畏邢。

id *autoreleaseNoPage(id obj)
{
    // Install the first page.
    // 初始化一個(gè)AutoreleasePoolPage
    // 當(dāng)前內(nèi)存中不存在AutoreleasePoolPage,則從頭開始構(gòu)建AutoreleasePoolPage,也就是其parent為nil
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    // 將初始化的AutoreleasePoolPage設(shè)置成hotPage
    setHotPage(page);
    
    // Push a boundary on behalf of the previously-placeholder'd pool.
    // 添加一個(gè)邊界對(duì)象(nil)
    if (pushExtraBoundary) {
        page->add(POOL_BOUNDARY);
    }
    
    // Push the requested object or pool.
    // 將對(duì)象添加到AutoreleasePoolPage中
    return page->add(obj);
}

當(dāng)前內(nèi)存中不存在AutoreleasePoolPage,則從頭開始構(gòu)建AutoreleasePoolPage,也就是其parentnil业扒。初始化之后,將當(dāng)前頁(yè)標(biāo)記為hotPage舒萎,然后會(huì)先向這個(gè)page中添加一個(gè)POOL_SENTINEL 對(duì)象程储,來(lái)確保在pop調(diào)用的時(shí)候,不會(huì)出現(xiàn)異常臂寝。最后章鲤,將對(duì)象添加到自動(dòng)釋放池中。

2.4交煞、pop方法

上面小節(jié)我們探索了objc_autoreleasePoolPush,下面我們看看objc_autoreleasePoolPop咏窿。
objc_autoreleasePoolPop的本質(zhì)就是調(diào)用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();
        }
    }
}

上面方法做了如下幾件事:

  1. 調(diào)用pageForPointer獲取當(dāng)前token所在的page素征。
  2. 調(diào)用releaseUntil方法釋放棧中的對(duì)象集嵌,直到stop
  3. 調(diào)用childkill方法御毅。

2.4.1根欧、pageForPointer找到page

pageForPointer方法主要是通過(guò)通過(guò)內(nèi)存地址的操作,獲取當(dāng)前token所在頁(yè)的首地址端蛆。

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

將指針與頁(yè)面的大小(4096)取模凤粗,可以得到當(dāng)前指針的偏移量。然后將指針的地址減偏移量便可以得到首地址今豆。

2.4.2嫌拣、releaseUntil釋放對(duì)象

void releaseUntil(id *stop) 
{
    // Not recursive: we don't want to blow out the stack 
    // if a thread accumulates a stupendous amount of garbage
    
    // 釋放AutoreleasePoolPage中的對(duì)象,直到next指向stop
    while (this->next != stop) {
        // Restart from hotPage() every time, in case -release 
        // autoreleased more objects
        // hotPage可以理解為當(dāng)前正在使用的page
        AutoreleasePoolPage *page = hotPage();

        // fixme I think this `while` can be `if`, but I can't prove it
        // 如果page為空的話呆躲,將page指向上一個(gè)page
        // 注釋寫到猜測(cè)這里可以使用if异逐,我感覺也可以使用if
        // 因?yàn)楦鶕?jù)AutoreleasePoolPage的結(jié)構(gòu),理論上不可能存在連續(xù)兩個(gè)page都為空
        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect();
        // obj = page->next; page->next--;
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        // POOL_BOUNDARY為nil插掂,是哨兵對(duì)象
        if (obj != POOL_BOUNDARY) {
            // 釋放obj對(duì)象
            objc_release(obj);
        }
    }

    // 重新設(shè)置hotPage
    setHotPage(this);

#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        assert(page->empty());
    }
#endif
}

因?yàn)?code>AutoreleasePool實(shí)際上就是由AutoreleasePoolPage組成的雙向鏈表灰瞻,因此腥例,*stop可能不是在最新的AutoreleasePoolPage中,即hotPage酝润,這時(shí)需要從hotPage開始燎竖,一直釋放,直到stop要销,中間所經(jīng)過(guò)的所有AutoreleasePoolPage里面的對(duì)象都要釋放构回。
對(duì)象的釋放objc_release方法請(qǐng)移步前面的文章iOS內(nèi)存管理一:Tagged Pointer&引用計(jì)數(shù)

2.4.3疏咐、kill方法

kill方法刪除雙向鏈表中的每一個(gè)page

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;
    // 找到鏈表最末尾的page
    while (page->child) page = page->child;

    AutoreleasePoolPage *deathptr;
    // 循環(huán)刪除每一個(gè)page
    do {
        deathptr = page;
        page = page->parent;
        if (page) {
            page->unprotect();
            page->child = nil;
            page->protect();
        }
        delete deathptr;
    } while (deathptr != this);
}

3捐凭、自動(dòng)釋放池和線程

官方文檔Using Autorelease Pool Blocks中關(guān)于自動(dòng)釋放池和線程的關(guān)系有如下一段描述。

Each thread in a Cocoa application maintains its own stack of autorelease pool blocks. If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block.
If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block.

翻譯成中文如下:

應(yīng)用程序中的每個(gè)線程都維護(hù)自己的自動(dòng)釋放池塊堆棧凳鬓。如果您正在編寫一個(gè)僅限基礎(chǔ)的程序,或者正在分離一個(gè)線程患民,那么您需要?jiǎng)?chuàng)建自己的自動(dòng)釋放池塊缩举。
如果您的應(yīng)用程序或線程是長(zhǎng)生命周期的,并且可能會(huì)生成大量的自動(dòng)釋放對(duì)象匹颤,那么您應(yīng)該使用自動(dòng)釋放池塊(如在主線程上使用AppKit和UIKit);否則仅孩,自動(dòng)釋放的對(duì)象會(huì)累積,內(nèi)存占用會(huì)增加印蓖。如果分離的線程不進(jìn)行Cocoa調(diào)用辽慕,則不需要使用自動(dòng)釋放池塊。

從上面這段秒速我們可以知道自動(dòng)釋放池和線程是緊密相關(guān)的赦肃,每一個(gè)自動(dòng)釋放池只對(duì)應(yīng)一個(gè)線程溅蛉。

4、AutoreleasePool和RunLoop

一般很少會(huì)將自動(dòng)釋放池和RunLoop聯(lián)系起來(lái)他宛,但是如果打印[NSRunLoop currentRunLoop]結(jié)果中會(huì)發(fā)現(xiàn)和自動(dòng)釋放池相關(guān)的回調(diào)船侧。

<CFRunLoopObserver 0x6000024246e0 [0x7fff8062ce20]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}
<CFRunLoopObserver 0x600002424640 [0x7fff8062ce20]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}

即App啟動(dòng)后,蘋果會(huì)給RunLoop注冊(cè)很多個(gè)observers厅各,其中有兩個(gè)是跟自動(dòng)釋放池相關(guān)的镜撩,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()。\

  • 第一個(gè)observer監(jiān)聽的是activities=0x1(kCFRunLoopEntry)队塘,也就是在即將進(jìn)入loop時(shí)袁梗,其回調(diào)會(huì)調(diào)用_objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池;
  • 第二個(gè)observer監(jiān)聽的是activities = 0xa0(kCFRunLoopBeforeWaiting | kCFRunLoopExit)憔古,
    即監(jiān)聽的是準(zhǔn)備進(jìn)入睡眠和即將退出loop兩個(gè)事件遮怜。在準(zhǔn)備進(jìn)入睡眠之前,因?yàn)樗呖赡軙r(shí)間很長(zhǎng)投放,所以為了不占用資源先調(diào)用_objc_autoreleasePoolPop()釋放舊的釋放池奈泪,并調(diào)用_objc_autoreleasePoolPush() 創(chuàng)建新建一個(gè)新的,用來(lái)裝載被喚醒后要處理的事件對(duì)象;在最后即將退出loop時(shí)則會(huì) _objc_autoreleasePoolPop()釋放池子涝桅。

5拜姿、總結(jié)

  1. 自動(dòng)釋放池是由AutoreleasePoolPage以雙向鏈表的方式實(shí)現(xiàn)的。
  2. 當(dāng)對(duì)象調(diào)用autorelease方法時(shí)冯遂,會(huì)將對(duì)象加入AutoreleasePoolPage的棧中蕊肥。
  3. 調(diào)用AutoreleasePoolPage::pop方法會(huì)向棧中的對(duì)象發(fā)送release消息。
  4. 自動(dòng)釋放池和線程是緊密相關(guān)的蛤肌,每一個(gè)自動(dòng)釋放池只對(duì)應(yīng)一個(gè)線程

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壁却,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子裸准,更是在濱河造成了極大的恐慌展东,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炒俱,死亡現(xiàn)場(chǎng)離奇詭異盐肃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)权悟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門砸王,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人峦阁,你說(shuō)我怎么就攤上這事谦铃。” “怎么了榔昔?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵驹闰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我件豌,道長(zhǎng)疮方,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任茧彤,我火速辦了婚禮骡显,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘曾掂。我一直安慰自己惫谤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布珠洗。 她就那樣靜靜地躺著溜歪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪许蓖。 梳的紋絲不亂的頭發(fā)上蝴猪,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天调衰,我揣著相機(jī)與錄音,去河邊找鬼自阱。 笑死嚎莉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沛豌。 我是一名探鬼主播趋箩,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼加派!你這毒婦竟也來(lái)了叫确?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤芍锦,失蹤者是張志新(化名)和其女友劉穎竹勉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娄琉,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饶米,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了车胡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡照瘾,死狀恐怖匈棘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情析命,我是刑警寧澤主卫,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站鹃愤,受9級(jí)特大地震影響簇搅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜软吐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一瘩将、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凹耙,春花似錦姿现、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至意述,卻和暖如春提佣,著一層夾襖步出監(jiān)牢的瞬間吮蛹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工拌屏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留潮针,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓槐壳,卻偏偏與公主長(zhǎng)得像然低,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子务唐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容