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)觀察實(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全局變量用于觀察目標(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)

  • 蘋果通過(guò)聲明一個(gè)__AtAutoreleasePool類型的局部變量__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類中的方法犁钟。于是我們可以斷定棱诱,AutoreleasePool是通過(guò)AutoreleasePoolPage類來(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是按線程一一對(duì)應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程)
3醋粟、AutoreleasePoolPage每個(gè)對(duì)象會(huì)開辟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的空間被占滿時(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 已滿肥照。

下面來(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存在且不滿的情況下勤众,直接將需要autorelease的對(duì)象加入棧中
        } else if (page) {
            return autoreleaseFullPage(obj, page);    // 在這個(gè)page已經(jīng)滿了的情況下,新建一個(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);   // 從棧頂開始操作出棧窥突,并向棧中的對(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、你編寫的程序不是基于 UI 框架的衡未,比如說(shuō)命令行工具尸执;
2、你編寫的循環(huán)中創(chuàng)建了大量的臨時(shí)對(duì)象缓醋;
3如失、你創(chuàng)建了一個(gè)輔助線程。

參考鏈接

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)系作者
  • 序言:七十年代末送粱,一起剝皮案震驚了整個(gè)濱河市岖常,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌葫督,老刑警劉巖竭鞍,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異橄镜,居然都是意外死亡偎快,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門洽胶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)晒夹,“玉大人,你說(shuō)我怎么就攤上這事姊氓∝で樱” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵翔横,是天一觀的道長(zhǎng)读跷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)禾唁,這世上最難降的妖魔是什么效览? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任无切,我火速辦了婚禮,結(jié)果婚禮上丐枉,老公的妹妹穿的比我還像新娘哆键。我一直安慰自己,他們只是感情好瘦锹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布籍嘹。 她就那樣靜靜地躺著,像睡著了一般弯院。 火紅的嫁衣襯著肌膚如雪辱士。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天抽兆,我揣著相機(jī)與錄音识补,去河邊找鬼。 笑死辫红,一個(gè)胖子當(dāng)著我的面吹牛凭涂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贴妻,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼切油,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了名惩?” 一聲冷哼從身側(cè)響起澎胡,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娩鹉,沒(méi)想到半個(gè)月后攻谁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弯予,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年戚宦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锈嫩。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡受楼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呼寸,到底是詐尸還是另有隱情艳汽,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布对雪,位于F島的核電站河狐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜甚牲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一义郑、第九天 我趴在偏房一處隱蔽的房頂上張望蝶柿。 院中可真熱鬧丈钙,春花似錦、人聲如沸交汤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)芙扎。三九已至星岗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戒洼,已是汗流浹背俏橘。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留圈浇,地道東北人寥掐。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像磷蜀,于是被迫代替她去往敵國(guó)和親召耘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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