AutoreleasePool

在iOS內(nèi)存管理中,在ARC機(jī)制中,我們通常使用AutoreleasePool進(jìn)行內(nèi)存管理伦仍,本篇文章我們主要來(lái)分析autoreleasePool的原理。

分析

我們?cè)陂_(kāi)發(fā)中 使用 @autoreleasepool{}方法將代碼運(yùn)行的對(duì)象痹籍,交給自動(dòng)釋放池管理呢铆,首先我們將以下代碼轉(zhuǎn)化為c++函數(shù)

int main(int argc, const char * argv[]) {

    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        NSLog(@"%@",obj);
    }
    return 0;
}

經(jīng)clang編譯后,我們可以看到autoreleasepool的結(jié)構(gòu)為如下結(jié)構(gòu)體

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

我們可以看出蹲缠,在__AtAutoreleasePool構(gòu)造方法中調(diào)用了objc_autoreleasePoolPush方法棺克,在析構(gòu)方法中調(diào)用了objc_autoreleasePoolPop方法。

AutoreleasePoolPage

我們?cè)谠创a中找到了objc_autoreleasePoolPush的實(shí)現(xiàn)

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

·AutoreleasePoolPage是什么呢线定?

struct AutoreleasePoolPageData
{
    magic_t const magic;
    __unsafe_unretained id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};

它繼承自AutoreleasePoolPageData娜谊,是雙向鏈表結(jié)構(gòu),其大小為 504 * 8 + 56斤讥,一頁(yè)能存儲(chǔ) 504個(gè)8字節(jié) 的對(duì)象

AutoreleasePoolPage.png

  • magic: 用來(lái)校驗(yàn)AutoreleasePoolPage的完整性纱皆。
  • next: 指向 最新添加到 AutoreleasePoolPage的對(duì)象湾趾,初始化時(shí)指向 begin
  • thread: 與 page綁定的線程。
  • parent: 指向上一頁(yè)派草,第一頁(yè)parent指向nil搀缠。
  • child: 指向下一頁(yè),最后一頁(yè)child指向nil近迁。
  • depth: 代表頁(yè)的深度艺普,每增加一頁(yè),depth 加 1鉴竭。
  • hiwat: 入棧對(duì)象的個(gè)數(shù)歧譬,即添加到該頁(yè)中對(duì)象的數(shù)量。

最后一頁(yè)為 hotPage搏存,其余頁(yè)為coldPage瑰步。

AutoreleasePoolPageData聯(lián)系.png

autoreleasePoolPush

在 push方法中

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::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;
    }
    static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) { 
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

autoreleaseFast方法中,實(shí)現(xiàn)了向自動(dòng)釋放池添加對(duì)象的操作璧眠。

那它是如何向 page中添加對(duì)象的呢缩焦?
1: 首先會(huì)根據(jù)當(dāng)前threadkey,獲取hotPage责静。
2 : 如果沒(méi)有hotPage舌界,說(shuō)明還沒(méi)有page
2.1:創(chuàng)建一個(gè)新的 page泰演。
2.2: 將當(dāng)前page設(shè)置為 hotPage呻拌。
2.3: 添加一個(gè)哨兵對(duì)象(邊界)。
2.4: 將對(duì)象添加到 hot頁(yè)中睦焕。

3: 如果有hotPage藐握,會(huì)判斷hotPage頁(yè)是否已滿
3.1: 如果滿了,會(huì)根據(jù)child找到最后一頁(yè)垃喊,然后新建一個(gè) poolPage猾普,將新創(chuàng)建的頁(yè)設(shè)置為 hotPage,最后將對(duì)象添加到該頁(yè)中。
3.2: 如果沒(méi)滿本谜,直接將對(duì)象添加到hotPage中初家。

哨兵對(duì)象(邊界)只有在調(diào)用objc_autoreleasePoolPush方法中才會(huì)添加。

整體過(guò)程如下圖所示:


autoreleasePoolPush.png

add

對(duì)象是怎樣添加到 hotPage中的呢乌助?

 id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }

通過(guò)next指針溜在,next指向page頁(yè)最后一個(gè)對(duì)象,將新對(duì)象添加到最后一個(gè)對(duì)象的后面他托。

objc_autoreleasePoolPop

在自動(dòng)釋放池進(jìn)行釋放的時(shí)候會(huì)調(diào)用 objc_autoreleasePoolPop(poolObj)方法掖肋。

    static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
            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 (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }

        return popPage<false>(token, page, stop);
    }

1,如果poolObj不為空,在判斷poolobj是不是begin且沒(méi)有父頁(yè)赏参,那么該autoreleasepool已沒(méi)有對(duì)象志笼。

2沿盅,如果 *poolobjc為哨兵對(duì)象(邊界),則執(zhí)行 popPage函數(shù)

    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && 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();
            }
        }
    }
    void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

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

            if (obj != POOL_BOUNDARY) {
                objc_release(obj);
            }
        }

        setHotPage(this);

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

我們來(lái)著重分析releaseUntil函數(shù)纫溃,在releaseUntil方法中

  • 1腰涧,先找到hotPage
  • 2紊浩,操作next指針南窗,對(duì) obj 執(zhí)行 objc_release操作,再偏移sizeof(obj)大小找到下一個(gè)obj郎楼。
  • 3,對(duì)當(dāng)前頁(yè)所有對(duì)象release完后窒悔,將其 parent頁(yè)設(shè)置為hotpage呜袁,重復(fù)該過(guò)程。

其過(guò)程如下圖所示:


objc_autoreleasePoolPop.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末简珠,一起剝皮案震驚了整個(gè)濱河市阶界,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聋庵,老刑警劉巖膘融,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異祭玉,居然都是意外死亡氧映,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)脱货,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)岛都,“玉大人,你說(shuō)我怎么就攤上這事振峻【室撸” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵扣孟,是天一觀的道長(zhǎng)烫堤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)凤价,這世上最難降的妖魔是什么鸽斟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮利诺,結(jié)果婚禮上湾盗,老公的妹妹穿的比我還像新娘。我一直安慰自己立轧,他們只是感情好格粪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布躏吊。 她就那樣靜靜地躺著,像睡著了一般帐萎。 火紅的嫁衣襯著肌膚如雪比伏。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天疆导,我揣著相機(jī)與錄音赁项,去河邊找鬼。 笑死澈段,一個(gè)胖子當(dāng)著我的面吹牛悠菜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播败富,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悔醋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了兽叮?” 一聲冷哼從身側(cè)響起芬骄,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹦聪,沒(méi)想到半個(gè)月后账阻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泽本,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年淘太,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片规丽。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琴儿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嘁捷,到底是詐尸還是另有隱情造成,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布雄嚣,位于F島的核電站晒屎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缓升。R本人自食惡果不足惜鼓鲁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望港谊。 院中可真熱鬧骇吭,春花似錦、人聲如沸歧寺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至龙致,卻和暖如春蛀缝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背目代。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工屈梁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人榛了。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓在讶,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親霜大。 傳聞我的和親對(duì)象是個(gè)殘疾皇子构哺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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