AutoReleasePoolPage

AutoReleasePoolPage

類(lèi)的定義

class AutoreleasePoolPage 
{
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;
    static size_t const SIZE = PAGE_MAX_SIZE;

    static size_t const COUNT = SIZE / sizeof(id);

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

成員變量解析:

  • key:用于創(chuàng)建線(xiàn)程的標(biāo)識(shí)
  • SCRIBBLE:刷子秀菱,可以不需要關(guān)心
  • SIZE:4096Bytes
  • COUNT:4096/8
  • magic:用于驗(yàn)證 AutoReleasePoolPage 的完整性
  • next:棧頂指針
  • thread: 當(dāng)前線(xiàn)程
  • parent: 父節(jié)點(diǎn), AutoReleasePoolPage 是一個(gè)雙向鏈表次员,這個(gè)parent也就是指向上一個(gè)節(jié)點(diǎn)
  • child: 子節(jié)點(diǎn)(下一個(gè)節(jié)點(diǎn))
  • depth: 深度,標(biāo)識(shí)這是第幾個(gè)節(jié)點(diǎn),從0開(kāi)始計(jì)數(shù)
  • hiwat: high water mark 高水位線(xiàn)图呢,用來(lái)報(bào)警內(nèi)存占用

就如前面所說(shuō),AutoReleasePoolPage 他是一個(gè)雙向鏈表骗随,每個(gè)節(jié)點(diǎn)大小為4096Bytes蛤织,而且前面56Bytes是成員函數(shù)所占有的。而且每個(gè)節(jié)點(diǎn)中有一個(gè)堆棧鸿染,其中next指針是指向棧頂指蚜。

begin()end()兩個(gè)方法分辨指向棧底和對(duì)象的結(jié)尾:

begin

id * begin() {
    return (id *) ((uint8_t *)this+sizeof(*this));
}

end

id * end() {
    return (id *) ((uint8_t *)this+SIZE);
}

標(biāo)準(zhǔn)的進(jìn)棧操作

add

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

出棧

releaseUntil

void releaseUntil(id *stop) 
{        
    while (this->next != stop) {

        AutoreleasePoolPage *page = hotPage();

        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
}

這里的出棧跟普通出棧不太一樣,因?yàn)檫@里的出棧會(huì)一直遞歸出棧到stop才會(huì)停止涨椒,這里的SCRIBBLE就是用來(lái)填寫(xiě)空白內(nèi)存區(qū)的摊鸡。

以上基本上算這個(gè)類(lèi)的定義內(nèi)容的解釋。

那么這個(gè)類(lèi)有什么作用呢蚕冬?

用來(lái)管理實(shí)現(xiàn)垃圾自動(dòng)回收免猾。

怎么實(shí)現(xiàn)垃圾回收?

void autoReleaseTest(void)
{
    void *pool = objc_autoreleasePoolPush();
    
    ........
    
    objc_autoreleasePoolPop(pool);

}

每次有一個(gè)加一個(gè)AutoReleasePool囤热,編譯器自動(dòng)回報(bào)我們解析成以上的形式猎提。那么看一下objc_autoreleasePoolPush()objc_autoreleasePoolPop(pool)方法的實(shí)現(xiàn)

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

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

也就是要先看看:

push

static inline void *push() 
{
    id *dest;
    if (DebugPoolAllocation) {
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}

這里可以看到Debug實(shí)現(xiàn)的方式不太一樣,

Debug是每次push都會(huì)創(chuàng)建一個(gè)新的page

然而非Debug卻不是

這應(yīng)該是為了Release節(jié)省內(nèi)存設(shè)計(jì)的旁蔼。

所以Debug環(huán)境會(huì)比Release環(huán)境內(nèi)存耗費(fèi)的多一點(diǎn)吧锨苏。

至于這里的POOL_BOUNDARY 是被define成一個(gè)nil對(duì)象的,用處就是一個(gè)哨兵對(duì)象的作用棺聊。

說(shuō)一下哨兵的作用:

哨兵節(jié)點(diǎn)通常被用在鏈表和遍歷樹(shù)中伞租,他并不擁有或引用任何被數(shù)據(jù)結(jié)構(gòu)管理的數(shù)據(jù)。常常用哨兵節(jié)點(diǎn)來(lái)替代null限佩,這樣的好處有:

  • 增加操作的速度
  • 降低算法的復(fù)雜性和代碼的大小
  • 增加數(shù)據(jù)結(jié)構(gòu)的魯棒性

簡(jiǎn)單的說(shuō)哨兵就是為了簡(jiǎn)化邊界條件的處理而存在的葵诈。

autoreleaseFast

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

根據(jù)不同的page狀態(tài)來(lái)進(jìn)行不同的操作

  • 當(dāng)page存在且未滿(mǎn)的時(shí)候:直接add object
  • 當(dāng)page存在但是已經(jīng)滿(mǎn)了的時(shí)候:先創(chuàng)建一個(gè)page,且當(dāng)前pagechild指向新page祟同,新pageparent指向當(dāng)前page作喘,然后add object
  • 當(dāng)page不存在的時(shí)候:創(chuàng)建一個(gè)新page,再add object

autoreleaseFullPage

static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    assert(page == hotPage());
    assert(page->full()  ||  DebugPoolAllocation);

    do {
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

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

hotpage()這個(gè)是當(dāng)前正在使用的page

autoreleaseNoPage

static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
    assert(!hotPage());

    bool pushExtraBoundary = false;
    if (haveEmptyPoolPlaceholder()) {
        pushExtraBoundary = true;
    }
    else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
        _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                     "autoreleased with no pool in place - "
                     "just leaking - break on "
                     "objc_autoreleaseNoPool() to debug", 
                     pthread_self(), (void*)obj, object_getClassName(obj));
        objc_autoreleaseNoPool(obj);
        return nil;
    }
    else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
        return setEmptyPoolPlaceholder();
    }
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);
    
    if (pushExtraBoundary) {
        page->add(POOL_BOUNDARY);
    }
    
    return page->add(obj);
}

add

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

這里的next是指向一個(gè)空指針的耐亏,注意一下徊都!
以上就是push操作了。

pop

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

    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        if (hotPage()) {
            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) {

        } else {
            return badPop(token);
        }
    }

    if (PrintPoolHiwat) printHiwat();

    page->releaseUntil(stop);

    if (DebugPoolAllocation  &&  page->empty()) {
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        page->kill();
        setHotPage(nil);
    } 
    else if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

pop操作比push要麻煩一些广辰!
簡(jiǎn)單的說(shuō)就是:

  • 找到token所在的page
  • 對(duì)token之后的所有對(duì)象暇矫,發(fā)送release消息
  • 殺掉多余的page

pageForPointer

static AutoreleasePoolPage *pageForPointer(const void *p) 
{
    return pageForPointer((uintptr_t)p);
}

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;
}
  • 先獲得偏移量
  • 通過(guò)當(dāng)前 p減去偏移量獲取 page指針
  • 檢測(cè)完整性
  • 返回page

releaseUntil

void releaseUntil(id *stop) 
{        
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();

        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

    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        assert(page->empty());
    }
#endif
}
  • 獲取hotpage
  • 判斷是否為空,非空進(jìn)入下一步择吊,為空獲取parent李根,并且設(shè)置parenthotpage,然后進(jìn)入上一步
  • 獲取棧頂對(duì)象,清空棧頂指針几睛,向棧頂對(duì)象發(fā)送release房轿,下移next指針
  • 判斷next是否等于stop,相等進(jìn)入下一步,不相等回到第一步
  • 設(shè)置hotpage

在debug環(huán)境中因?yàn)槊看?code>push會(huì)創(chuàng)建一個(gè)新的page囱持,每次pop會(huì)把空的pagekill掉夯接,所以不存在空的page

kill

if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
  • 當(dāng)page使用量小于一半纷妆,殺掉pagechild之后的所有page
  • 當(dāng)page的使用量大于一半且存在孫子的時(shí)候盔几,殺掉孫子之后的所有page
  • 當(dāng)page的使用量大于一半且不存在孫子的時(shí)候,保留child

這里為什么要保留一個(gè)child,可能大家覺(jué)得很奇怪,其實(shí)是為了節(jié)省開(kāi)銷(xiāo)掩幢,因?yàn)槿绻笥谝话氲臈1徽加昧酥蠛苡锌赡苎放模R上需要新建新的page對(duì)象。

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;
    while (page->child) page = page->child;

    AutoreleasePoolPage *deathptr;
    do {
        deathptr = page;
        page = page->parent;
        if (page) {
            page->unprotect();
            page->child = nil;
            page->protect();
        }
        delete deathptr;
    } while (deathptr != this);
}
  • 先找到雙向鏈表最后一個(gè)節(jié)點(diǎn)
  • 然后從最后一個(gè)節(jié)點(diǎn)開(kāi)始刪除
  • 直到刪除到當(dāng)前節(jié)點(diǎn)(包括當(dāng)前節(jié)點(diǎn))

總結(jié)

那么到目前為止AutoReleasePoolPage里面主要的流程已經(jīng)講完了际邻。其他的一些方法都是無(wú)關(guān)大雅的東西了芯丧。

實(shí)際上AutoReleasePoolPage只是做一個(gè)導(dǎo)師的角色,他并不會(huì)去銷(xiāo)毀對(duì)象(自己除外)世曾,但是他回去告訴他管理的對(duì)象現(xiàn)在該release一下缨恒。

有問(wèn)題的地方望各位大能指出來(lái)!看到我會(huì)改正的哦度硝!~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肿轨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蕊程,更是在濱河造成了極大的恐慌椒袍,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藻茂,死亡現(xiàn)場(chǎng)離奇詭異驹暑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)辨赐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)优俘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人掀序,你說(shuō)我怎么就攤上這事帆焕。” “怎么了不恭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵叶雹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我换吧,道長(zhǎng)折晦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任沾瓦,我火速辦了婚禮满着,結(jié)果婚禮上谦炒,老公的妹妹穿的比我還像新娘。我一直安慰自己风喇,他們只是感情好宁改,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著响驴,像睡著了一般透且。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上豁鲤,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音鲸沮,去河邊找鬼琳骡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛讼溺,可吹牛的內(nèi)容都是我干的楣号。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怒坯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼炫狱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起剔猿,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤视译,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后归敬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體酷含,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年汪茧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椅亚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舱污,死狀恐怖呀舔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扩灯,我是刑警寧澤媚赖,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站驴剔,受9級(jí)特大地震影響省古,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丧失,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一豺妓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦琳拭、人聲如沸训堆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坑鱼。三九已至,卻和暖如春絮缅,著一層夾襖步出監(jiān)牢的瞬間鲁沥,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工耕魄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留画恰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓吸奴,卻偏偏與公主長(zhǎng)得像允扇,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子则奥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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