Autorelease Pool 知識(shí)匯總

一、Autorelease Pool是什么

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

二、Autorelease對(duì)象到底什么時(shí)候被釋放

很多人說(shuō)在“當(dāng)前作用域大括號(hào)結(jié)束時(shí)釋放”,顯然沒(méi)有正確理解Autorelease機(jī)制。
在沒(méi)有手加 Autorelease Pool的情況下,Autorelease 對(duì)象是在當(dāng)前的 runloop迭代結(jié)束 時(shí)釋放的,而它能夠釋放的原因是系統(tǒng)在每個(gè) runloop 迭代中都加入了 自動(dòng)釋放池Push和Pop

三、在MRC中,調(diào)用[obj autorelease]來(lái)延遲內(nèi)存的釋放是一件簡(jiǎn)單自然的事,ARC下,我們甚至可以完全不知道Autorelease就能管理好內(nèi)存。而在這背后,objc和編譯器都幫我們做了哪些事呢?

如下Demo:

#import <Foundation/Foundation.h>
// 生成兩個(gè)全局weak變量用來(lái)觀(guān)察實(shí)驗(yàn)對(duì)象
__weak NSString *weak_String;
__weak NSString *weak_StringAutorelease;

- (void)viewDidLoad {
    [super viewDidLoad];

    @autoreleasepool {
        [self createStr];
        NSLog(@"------in the autoreleasepool------");
        NSLog(@"%@", weak_String);
        NSLog(@"%@\n\n", weak_StringAutorelease);
    }
    NSLog(@"------in the main()------");
    NSLog(@"%@", weak_String);
    NSLog(@"%@\n\n", weak_StringAutorelease);
}

-(void)createStr
{
    // 創(chuàng)建常規(guī)對(duì)象
    NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"];
    // 創(chuàng)建autorelease對(duì)象
    NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"];
    weak_String = string;
    weak_StringAutorelease = stringAutorelease;
    
    NSLog(@"------in the createString()------");
    NSLog(@"%@", weak_String);
    NSLog(@"%@\n\n", weak_StringAutorelease);
}

運(yùn)行結(jié)果如下:

屏幕快照 2019-02-21 下午2.50.03.png
  • 首先在createString函數(shù)中創(chuàng)建了一個(gè)常規(guī)NSString對(duì)象和一個(gè)autorelease對(duì)象,然后分別賦值給兩個(gè)weak全局變量用于觀(guān)察目標(biāo)對(duì)象。通過(guò)打印結(jié)果可以看到,在createString函數(shù)中兩個(gè)對(duì)象都是正常存在的,出了createString函數(shù)在autoreleasepool中,常規(guī)對(duì)象已經(jīng)被釋放,而autorelease對(duì)象依然存在。在autoreleasepool外,autorelease對(duì)象也被釋放了。

四、AutoreleasePool 的底層實(shí)現(xiàn)

  • 蘋(píng)果通過(guò)聲明一個(gè)__AtAutoreleasePool類(lèi)型的局部變量__autoreleasepool實(shí)現(xiàn)了@autoreleasepool{}
  • 看看__AtAutoreleasePool的定義:
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

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

我們可以分析出,單個(gè)自動(dòng)釋放池的執(zhí)行過(guò)程就是:
objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)

五、AutoreleasePoolPage

objc_autoreleasePoolPush()函數(shù)的實(shí)現(xiàn):

void *
objc_autoreleasePoolPush(void)
{
    if (UseGC) return nil;
    return AutoreleasePoolPage::push();
}

同樣objc_autoreleasePoolPop()函數(shù)的實(shí)現(xiàn):

void
objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;
    AutoreleasePoolPage::pop(ctxt);
}

我們發(fā)現(xiàn)這兩個(gè)函數(shù)的實(shí)現(xiàn)都是調(diào)用了AutoreleasePoolPage類(lèi)中的方法。于是我們可以斷定,AutoreleasePool是通過(guò)AutoreleasePoolPage類(lèi)來(lái)實(shí)現(xiàn)的。

看看AutoreleasePoolPage定義的屬性:

magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

1、AutoreleasePool并沒(méi)有單獨(dú)的結(jié)構(gòu),而是由若干個(gè)AutoreleasePoolPage以雙向鏈表的形式組合而成(分別對(duì)應(yīng)結(jié)構(gòu)中的parent指針child指針
2、AutoreleasePool是按線(xiàn)程一一對(duì)應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線(xiàn)程)
3、AutoreleasePoolPage每個(gè)對(duì)象會(huì)開(kāi)辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁(yè)的大?。?,除了上面的實(shí)例變量所占空間,剩下的空間全部用來(lái)儲(chǔ)存autorelease對(duì)象的地址
4、上面的id *next指針作為游標(biāo)指向棧頂最新add進(jìn)來(lái)的autorelease對(duì)象的下一個(gè)位置,一個(gè)AutoreleasePoolPage的空間被占滿(mǎn)時(shí),會(huì)新建一個(gè)AutoreleasePoolPage對(duì)象,連接鏈表,后來(lái)的autorelease對(duì)象在新的page加入
5、棧中存放的指針指向加入需要release的對(duì)象或者POOL_SENTINEL(哨兵對(duì)象,用于分隔autoreleasepool)。
6、棧中指向POOL_SENTINEL的指針就是autoreleasepool的一個(gè)標(biāo)記。當(dāng)autoreleasepool進(jìn)行出棧操作,每一個(gè)比這個(gè)哨兵對(duì)象后進(jìn)棧的對(duì)象都會(huì)被release。

image

  • 當(dāng) next == begin() 時(shí),表示 AutoreleasePoolPage 為空;
    當(dāng) next == end() 時(shí),表示 AutoreleasePoolPage 已滿(mǎn)。

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

AutoreleasePoolPage::push()

   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存在且不滿(mǎn)的情況下,直接將需要autorelease的對(duì)象加入棧中
        } else if (page) {
            return autoreleaseFullPage(obj, page);    // 在這個(gè)page已經(jīng)滿(mǎn)了的情況下,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
        } else {
            return autoreleaseNoPage(obj);    // 在沒(méi)有page的情況下,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
        }
    }

autoreleaseFullPage(obj, page)autoreleaseNoPage(obj)的區(qū)別在于autoreleaseFullPage(obj, page)會(huì)將當(dāng)前page的child指向新建的page,而autoreleaseNoPage(obj)會(huì)在新建的page中先入棧一個(gè)POOL_SENTINEL(哨兵對(duì)象),再將obj入棧。

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

AutoreleasePoolPage::pop(ctxt)

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

AutoreleasePoolPage::autorelease((id)this);

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

autorelease函數(shù)和push函數(shù)一樣,關(guān)鍵代碼都是調(diào)用autoreleaseFast函數(shù)向自動(dòng)釋放池的鏈表?xiàng)V刑砑右粋€(gè)對(duì)象,不過(guò)push函數(shù)的入棧的是一個(gè)哨兵對(duì)象,而autorelease函數(shù)入棧的是需要加入autoreleasepool的對(duì)象。

六、補(bǔ)充

使用容器的block版本的枚舉器時(shí),內(nèi)部會(huì)自動(dòng)添加一個(gè)AutoreleasePool:

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 這里被一個(gè)局部@autoreleasepool包圍著
}];

普通for循環(huán)和for in循環(huán)中沒(méi)有,for循環(huán)中遍歷產(chǎn)生大量autorelease變量時(shí),就需要手加局部AutoreleasePool。

  • 下面三種情況下是需要我們手動(dòng)添加 autoreleasepool 的:

1、你編寫(xiě)的程序不是基于 UI 框架的,比如說(shuō)命令行工具;
2、你編寫(xiě)的循環(huán)中創(chuàng)建了大量的臨時(shí)對(duì)象;
3、你創(chuàng)建了一個(gè)輔助線(xiàn)程。

參考鏈接

http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/#jtss-tsina

?著作權(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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