淺談AutoreleasePool底層實(shí)現(xiàn)原理

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

AutoreleasePool創(chuàng)建和釋放

  • App啟動(dòng)后,蘋果在主線程 RunLoop 里注冊了兩個(gè) Observer,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()。
  • 第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池。其 order 是-2147483647,優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
  • 第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來釋放自動(dòng)釋放池。這個(gè) Observer 的 order 是 2147483647,優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
  • 在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的。這些回調(diào)會(huì)被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會(huì)出現(xiàn)內(nèi)存泄漏,開發(fā)者也不必顯示創(chuàng)建 Pool 了。

也就是說AutoreleasePool創(chuàng)建是在一個(gè)RunLoop事件開始之前(push),AutoreleasePool釋放是在一個(gè)RunLoop事件即將結(jié)束之前(pop)。
AutoreleasePool里的Autorelease對象的加入是在RunLoop事件中,AutoreleasePool里的Autorelease對象的釋放是在AutoreleasePool釋放時(shí)。

AutoreleasePool實(shí)現(xiàn)原理

在終端中使用clang -rewrite-objc命令將下面的OC代碼重寫成C++的實(shí)現(xiàn)(參考:使用clang將OC代碼轉(zhuǎn)為C++
):
1)@autoreleasepool實(shí)質(zhì)上是一個(gè)__AtAutoreleasePool的結(jié)構(gòu)體對象;
下面是main 函數(shù)的C++實(shí)現(xiàn),聲明了一個(gè)__AtAutoreleasePool對象,然后調(diào)用應(yīng)用程序入口函數(shù);

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ {
        __AtAutoreleasePool __autoreleasepool;
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

__AtAutoreleasePool的結(jié)構(gòu)如下所示:

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

可以看出來,當(dāng)申明了一個(gè)對象__autoreleasepool,相當(dāng)于調(diào)用了objc_autoreleasePoolPush()函數(shù),該函數(shù)的作用即向堆棧內(nèi)壓入一個(gè)“自動(dòng)釋放池”;而當(dāng)int main()函數(shù)執(zhí)行完畢后,執(zhí)行__autoreleasepool的析構(gòu)函數(shù)objc_autoreleasePoolPop(atautoreleasepoolobj),用于釋放“自動(dòng)釋放池”;所以main()函數(shù)里可以大致這樣理解:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ {
        //創(chuàng)建自動(dòng)釋放池
        __AtAutoreleasePool __autoreleasepool = objc_autoreleasePoolPush();
        //TODO 執(zhí)行各種操作,將對象加入自動(dòng)釋放池
        
        //釋放自動(dòng)釋放池
        objc_autoreleasePoolPop(__autoreleasepool)
    }
}

2) AutoreleasePool的本質(zhì)

已經(jīng)有人在蘋果的官方源碼里找到了關(guān)于AutoreleasePool的底層實(shí)現(xiàn)NSObject.mm里,源碼地址:https://opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm.auto.html
objc_autoreleasePoolPush()objc_autoreleasePoolPop(__autoreleasepool)的實(shí)現(xiàn)如下:

void* objc_autoreleasePoolPush(void)
{
    if (UseGC) return NULL; //如果使用垃圾回收機(jī)制
    return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;

    // fixme rdar://9167170
    if (!ctxt) return;

    AutoreleasePoolPage::pop(ctxt);
}

從上面可以發(fā)現(xiàn),C++類AutoreleasePoolPage才是實(shí)際的實(shí)現(xiàn)所在,找到AutoreleasePoolPage:

class AutoreleasePoolPage 
{
#define POOL_SENTINEL 0
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        4096;  // must be multiple of vm page size
#else
        4096;  // size and alignment, power of 2
#endif
    magic_t const magic; //用于數(shù)據(jù)校驗(yàn)
    id *next;            //棧頂?shù)刂?    pthread_t const thread; //所在的線程
    AutoreleasePoolPage * const parent; //父對象
    AutoreleasePoolPage *child; //子對象
    uint32_t const depth;   //page的序號(hào)?
    uint32_t hiwat;
    ...
}

去除了一些不重要的代碼,可以看出這是一個(gè)典型的雙向列表結(jié)構(gòu),每個(gè)Page大小為4096 Byte,所以AutoreleasePool實(shí)質(zhì)上是一個(gè)雙向AutoreleasePoolPage列表;接下來分析一下自動(dòng)釋放池的工作過程:

創(chuàng)建自動(dòng)釋放池

void* objc_autoreleasePoolPush()內(nèi)部實(shí)際調(diào)用的是AutoreleasePoolPage::push()函數(shù),其實(shí)現(xiàn)如下:

static inline void *push() 
    {
        if (!hotPage()) {
            setHotPage(new AutoreleasePoolPage(NULL));
        } 
        id *dest = autoreleaseFast(POOL_SENTINEL);
        assert(*dest == POOL_SENTINEL);
        return dest;
    }

hotPage()是找出當(dāng)前的正在使用的page,第一次調(diào)用時(shí)hotPage為NULL,所以新建一個(gè)parent=NULL的AutoreleasePoolPage對象作為自動(dòng)釋放池加入棧中,并將其設(shè)置為hotPage,然后返回POOL_SENTINEL的地址賦值給main()函數(shù)里的變量 __autoreleasepool;

然后將一個(gè)哨兵對象POOL_SENTINEL壓入棧頂,即調(diào)用autoreleaseFast(POOL_SENTINEL)

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else {
            return autoreleaseSlow(obj);
        }
    }

添加對象進(jìn)自動(dòng)釋放池

可以看出,如果當(dāng)前有page并且沒有滿,則直接將對象入棧頂(page->add(obj)):

id *add(id obj)
    {
        assert(!full());
        unprotect();
        *next++ = obj;
        protect();
        return next-1;
    }

將對象壓入棧頂,然后將棧頂指針下移;
如果上述autoreleaseFast(id obj)中的page已經(jīng)滿了,則執(zhí)行autoreleaseSlow(obj)

id *autoreleaseSlow(id obj)
    {
        AutoreleasePoolPage *page;
        page = hotPage();
        //如果沒有page,則新建一個(gè)自動(dòng)釋放池,并添加obj對象進(jìn)釋放池
        if (!page) {
            objc_autoreleaseNoPool(obj);
            return NULL;
        }
        //如果當(dāng)前hotPage已經(jīng)滿了,則以鏈表的形式新增一個(gè)page并添加到當(dāng)前page的后面,然后將此設(shè)置為hotPage;
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

基本邏輯即,如果page不存在則,創(chuàng)建新的自動(dòng)釋放池(PoolPage),并將對象加進(jìn)池子;如果已經(jīng)存在自動(dòng)釋放池在棧中,且hotPage滿了,則遍歷其子page,如果存在沒滿(page->full()==NO)的子page,則將該子page設(shè)置為hotPage,否則如果都滿了,則以最后一個(gè)子page為父page,新建一個(gè)page,插入當(dāng)前的page鏈表,同樣設(shè)置該新建的page為hotPage,然后將自動(dòng)釋放對象加入page;

銷毀自動(dòng)釋放池

銷毀自動(dòng)釋放池的調(diào)用方式是:

void AutoreleasePoolPage::pop(void *token)

token 即push()的返回值,實(shí)際上就是POOL_SENTINEL的地址(__autoreleasepool)通過該地址即可找到所在Page的地址指針

static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token) {
            page = pageForPointer(token); //找到所在的page地址
            stop = (id *)token; //POOL_SENTINEL的地址,從棧頂釋放對象直到這個(gè)位置
        } else {
            // Token 0 is top-level pool
            page = coldPage();
            stop = page->begin();
        }

        page->releaseUntil(stop); //對自動(dòng)釋放池中對象調(diào)用objc_release()進(jìn)行釋放

        // memory: delete empty children
        // hysteresis: keep one empty child if this page is more than half full
        // special case: delete everything for pop(0)
        if (!token) {
            page->kill();
            setHotPage(NULL);
        } else if (page->child) {
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

該過程主要分為兩步:
一、page->releaseUntil(stop),對棧頂(page->next)到stop地址(POOL_SENTINEL)之間的所有對象調(diào)用objc_release(),進(jìn)行引用計(jì)數(shù)減1;
二、清空page對象page->kill(),有兩句注釋:

// hysteresis: keep one empty child if this page is more than half full
// special case: delete everything for pop(0)

除非是pop(0)方式調(diào)用,這樣會(huì)清理掉所有page對象;否則,在當(dāng)前page存放的對象大于一半時(shí),會(huì)保留一個(gè)空的子page,這樣估計(jì)是為了可能馬上需要新建page節(jié)省創(chuàng)建page的開銷吧.

對應(yīng)代碼

if (page->child) {
           if (page->lessThanHalfFull()) {
               page->child->kill(); //全部刪除
           }
           else if (page->child->child) {
               page->child->child->kill();//保留一個(gè)子page
           }
       }

如果當(dāng)前的page中存放的對象少于一半,則子page全部刪除;如果當(dāng)前當(dāng)前的page存放的多余一半(意味著馬上將要滿),則保留一個(gè)子page,節(jié)省創(chuàng)建新page的開銷;

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

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

  • AutoreleasePool(自動(dòng)釋放池)是OC中的一種內(nèi)存自動(dòng)回收機(jī)制,它可以延遲加入AutoreleaseP...
    jackyshan閱讀 10,583評(píng)論 5 34
  • 不管你是新手還是老鳥,工作中肯定逃不開autoreleasepool這個(gè)天使,因?yàn)樗龑?shí)在是一門藝術(shù),為我們現(xiàn)在的編...
    皇垚閱讀 2,633評(píng)論 0 3
  • 內(nèi)存管理一直是學(xué)習(xí) Objective-C 的重點(diǎn)和難點(diǎn)之一,盡管現(xiàn)在已經(jīng)是 ARC 時(shí)代了,但是了解 Objec...
    CholMay閱讀 3,949評(píng)論 0 20
  • 面試題: Autoreleasepool 里面的對象什么時(shí)候銷毀。這個(gè)問題經(jīng)常被拿來做面試題,問很多人,很少能答對...
    s_在路上閱讀 1,186評(píng)論 0 4
  • 無論何種感情, 不要成為一種負(fù)累。 若形成壓力,總要逃離; 若造就牽絆,總會(huì)失去。 在意,卻不刻意; 珍惜,卻不癡...
    小瑞行仔閱讀 79評(píng)論 0 0

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