在ARC環(huán)境中autoreleasepool(runloop)的研究

引言

最近有個(gè)大佬考察了我關(guān)于autoreleasepool的了解, 之前一直認(rèn)為自己了解, 但是稍微一問(wèn)深, 自己卻啞口無(wú)言. 仔細(xì)思考了下, 決定要將這個(gè)問(wèn)題結(jié)合之前的知識(shí)從新梳理一下, 當(dāng)然, 實(shí)踐是必不可少的.

  • main函數(shù)中的autoreleasepool的作用?
  • 系統(tǒng)的autoreleasepool我們自己創(chuàng)建的autoreleasepool釋放時(shí)機(jī)差別在哪?
  • 在ARC的環(huán)境中, 什么情況下需要使用autoreleasepool? 不使用autoreleasepool變量什么時(shí)候會(huì)被釋放?

帶著這三個(gè)問(wèn)題, 一起進(jìn)行一下下面的思考.

正文

對(duì)于autoreleasepool釋放時(shí)機(jī), 我們很容易在網(wǎng)上搜到這樣的說(shuō)法:

分兩種情況:手動(dòng)干預(yù)釋放時(shí)機(jī)食寡、系統(tǒng)自動(dòng)去釋放顷牌。

手動(dòng)干預(yù)釋放時(shí)機(jī)--指定autoreleasepool 就是所謂的:當(dāng)前作用域大括號(hào)結(jié)束時(shí)釋放遣疯。

系統(tǒng)自動(dòng)去釋放--不手動(dòng)指定autoreleasepool

先不談上面是否完全正確, 基于以上認(rèn)知, 當(dāng)時(shí)我靈光一閃推測(cè)main函數(shù)中autoreleasepool的作用可能為下面兩種之一:

1.系統(tǒng)主線程中的默認(rèn)的autoreleasepool.

2.整個(gè)App相對(duì)于iOS系統(tǒng)的一個(gè)autoreleasepool.

其他的解釋其實(shí)在網(wǎng)上可以搜到很多, 所以這里我們可以做一個(gè)小實(shí)驗(yàn).

第一點(diǎn)其實(shí)很好驗(yàn)證, 將main函數(shù)中的autoreleasepool注釋掉, 運(yùn)行

for (int i = 0; i < 10e5 * 2; i++) {
    NSString *str = [NSString stringWithFormat:@"hi + %d", i];
}
NSLog(@"finished!");

實(shí)際結(jié)果表明, 內(nèi)存波動(dòng)并沒(méi)有什么區(qū)別:

  • 未注釋Main函數(shù)中的autoreleasepool


  • 注釋Main函數(shù)中的autoreleasepool


所以我們可以認(rèn)為第二種是對(duì)的嗎, 后來(lái)自己一想也覺(jué)得不對(duì), 對(duì)于系統(tǒng)內(nèi)存管理相關(guān)代碼怎么會(huì)在程序里面呢, 不符合蘋(píng)果的風(fēng)格. 結(jié)果很明顯我自己推測(cè)的都不對(duì), 所以到底起什么作用呢? 待會(huì)再細(xì)說(shuō), 先驗(yàn)證一下釋放時(shí)機(jī)的問(wèn)題.

同樣是上面一段函數(shù), 在for循環(huán)中加入autoreleasepool:

for (int i = 0; i < 10e5 * 2; i++) {
    @autoreleasepool {
        NSString *str = [NSString stringWithFormat:@"hi + %d", i];
    }
}
NSLog(@"finished!");

我相信稍微了解一點(diǎn)的同學(xué)已經(jīng)知道了運(yùn)行結(jié)果:

為臨時(shí)變量分配的內(nèi)存已經(jīng)得到平穩(wěn)的釋放, 所以結(jié)論就是最上面我們看到的認(rèn)知? 其實(shí)本身每個(gè)Runloop已經(jīng)默認(rèn)會(huì)創(chuàng)建一個(gè)autoreleasepool了, 所以我們這里添加相當(dāng)于嵌套(便于理解)了一個(gè), 并沒(méi)有弄清楚autoreleasepool自身的釋放時(shí)機(jī). 下面做另外一個(gè)小測(cè)試:

這一次在代碼中新增對(duì)Runloop的Observer, 及時(shí)獲取Runloop的狀態(tài)變化確認(rèn)釋放時(shí)機(jī), 代碼如下:

// 添加一個(gè)監(jiān)聽(tīng)者
- (void)addRunLoopObserver {
    
    // 1. 創(chuàng)建監(jiān)聽(tīng)者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"進(jìn)入RunLoop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即將處理Timer事件");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即將處理Source事件");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即將休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"被喚醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"退出RunLoop");
                break;
            default:
                break;
        }
    });
    
    // 2. 添加監(jiān)聽(tīng)者
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}

另外上面的方法運(yùn)行連續(xù)運(yùn)行兩次, 不手動(dòng)添加autoreleasepool, 大概是這樣:

- (void)test1 {

    NSLog(@"test1 begin!");
    for (int i = 0; i < 10e5 * 2; i++) {
        //@autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"hi + %d", i];
        //}
    }
    NSLog(@"test1 finished!");
}

- (void)test2 {
    
    NSLog(@"test2 begin!");
    for (int i = 0; i < 10e5 * 2; i++) {
        //@autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"hi + %d", i];
        //}
    }
    NSLog(@"test2 finished!");
}

運(yùn)行之后的效果是這樣的:

很清楚的看到Runloop沒(méi)有完成一次循環(huán)之前所有內(nèi)存都未釋放, 即使局部變量出了作用域也必須等待Runloop循環(huán)完成.

下面同樣, 手動(dòng)添加autoreleasepool觀察釋放時(shí)機(jī).

結(jié)果是意外也合理的. 即使Runloop未完成循環(huán), 內(nèi)存也即使釋放了.

總結(jié)

@autoreleasepool{} 

等價(jià)于

void *context = objc_autoreleasePoolPush();
// {}中的代碼
objc_autoreleasePoolPop(context);

每次出了{(lán)}時(shí)objc_autoreleasePoolPop()就被調(diào)用, 所以直接釋放掉了. 當(dāng)然, 系統(tǒng)自動(dòng)創(chuàng)建的autoreleasepool也是一樣, 只是調(diào)用的時(shí)機(jī)不同: 線程與Runloop是一一對(duì)應(yīng), Runloop與系統(tǒng)創(chuàng)建的autoreleasepool也是一一對(duì)應(yīng), 所以不論是Runloop完成了一次循環(huán)還是線程被關(guān)閉時(shí), autoreleasepool都會(huì)釋放, 當(dāng)然手動(dòng)添加的也會(huì)被管理, 上面為了方便理解, 說(shuō)的是嵌套, 本質(zhì)上是沒(méi)有嵌套這個(gè)說(shuō)法的, 對(duì)@autoreleasepool{}本質(zhì)的一些個(gè)人總結(jié):

主要就是一個(gè)類(lèi):AutoreleasePoolPage

兩個(gè)函數(shù): objc_autoreleasePoolPush()、objc_autoreleasePoolPop()

運(yùn)作方式: autoreleasepool由若干個(gè)autoreleasePoolPage類(lèi)以雙向鏈表的形式組合而成, 當(dāng)程序運(yùn)行到@autoreleasepool{時(shí), objc_autoreleasePoolPush()將被調(diào)用, runtime會(huì)向當(dāng)前的AutoreleasePoolPage中添加一個(gè)nil對(duì)象作為哨兵,
在{}中創(chuàng)建的對(duì)象會(huì)被依次記錄到AutoreleasePoolPage的棧頂指針,
當(dāng)運(yùn)行完@autoreleasepool{}時(shí), objc_autoreleasePoolPop(哨兵)將被調(diào)用, runtime就會(huì)向AutoreleasePoolPage中記錄的對(duì)象發(fā)送release消息直到哨兵的位置, 即完成了一次完整的運(yùn)作.

另外根據(jù)官方文檔:

Threads

If you are making Cocoa calls outside of the Application Kit’s main thread—for example if you create a Foundation-only application or if you detach a thread—you need to create your own autorelease pool......

主線程中的自動(dòng)釋放池是自動(dòng)創(chuàng)建的, 文檔中說(shuō)子線程中的自動(dòng)釋放池是需要手動(dòng)創(chuàng)建的, 但實(shí)測(cè), 其實(shí)我們常用的多線程管理方式(GCD, NSOprationQueue, NSThread)都已經(jīng)幫我們處理好了, 其中NSThread在iOS7之后才自動(dòng)創(chuàng)建線程中的AutoreleasePool, 這個(gè)在官方文檔中找不到記錄, 參考StackOverflow: https://stackoverflow.com/questions/24952549/does-nsthread-create-autoreleasepool-automatically-now

另外網(wǎng)上有說(shuō)法AutoreleasePool會(huì)影響性能, 其實(shí)看上面的函數(shù)運(yùn)行的時(shí)間就可以發(fā)現(xiàn), 并沒(méi)有影響, 甚至加入了AutoreleasePool運(yùn)行快了2秒(不嚴(yán)謹(jǐn)).

回到最初的問(wèn)題, main函數(shù)中的autoreleasepool的作用, 我翻閱了大量資料, 在StackOverflow上贊的比較高的回答是沒(méi)卵用... 暫且只能先這樣認(rèn)為了.. 希望有了解的同學(xué)可以講解一下~

在實(shí)際中的使用場(chǎng)景其實(shí)很明確了, 在程序中中有大量臨時(shí)變量的時(shí)候最好手動(dòng)創(chuàng)建.

最常出現(xiàn)大量變量的時(shí)候顯然是循環(huán)/遍歷, 我們常用的for循環(huán), 以及enumerate其實(shí)跟autoreleasepool也有關(guān), for循環(huán)是不自動(dòng)創(chuàng)建autoreleasepool的, 而enumerate中已經(jīng)自動(dòng)創(chuàng)建了autoreleasepool, 值得注意的是高并發(fā)enumerate常常會(huì)出一些意外的問(wèn)題, 例如對(duì)象被提前釋放, 所以建議高并發(fā)情況下使用for循環(huán)(性能高于enumerate), 再手動(dòng)添加autoreleasepool.

本人前幾篇文章中提到的一個(gè)App: 直播伴侶中就是手機(jī)端對(duì)彈幕進(jìn)行高并發(fā)計(jì)算, 分詞, 對(duì)比.. 使用了autoreleasepool之后明顯在斗魚(yú)彈幕服務(wù)器"炸魚(yú)"時(shí)有所改善..歡迎Star: https://github.com/syik/BulletAnalyzer

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末又厉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子时甚,更是在濱河造成了極大的恐慌泻仙,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拱镐,死亡現(xiàn)場(chǎng)離奇詭異艘款,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)沃琅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)哗咆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人益眉,你說(shuō)我怎么就攤上這事晌柬。” “怎么了郭脂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵年碘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我展鸡,道長(zhǎng)屿衅,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任莹弊,我火速辦了婚禮傲诵,結(jié)果婚禮上凯砍,老公的妹妹穿的比我還像新娘。我一直安慰自己拴竹,他們只是感情好悟衩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著栓拜,像睡著了一般座泳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上幕与,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天挑势,我揣著相機(jī)與錄音,去河邊找鬼啦鸣。 笑死潮饱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诫给。 我是一名探鬼主播香拉,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼中狂!你這毒婦竟也來(lái)了凫碌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤胃榕,失蹤者是張志新(化名)和其女友劉穎盛险,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體勋又,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苦掘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了楔壤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸟蜡。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖挺邀,靈堂內(nèi)的尸體忽然破棺而出揉忘,到底是詐尸還是另有隱情,我是刑警寧澤端铛,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布泣矛,位于F島的核電站,受9級(jí)特大地震影響禾蚕,放射性物質(zhì)發(fā)生泄漏您朽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哗总。 院中可真熱鬧几颜,春花似錦、人聲如沸讯屈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涮母。三九已至谆趾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叛本,已是汗流浹背沪蓬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留来候,地道東北人跷叉。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像营搅,于是被迫代替她去往敵國(guó)和親云挟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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

  • 一剧防、什么是runloop 字面意思是“消息循環(huán)、運(yùn)行循環(huán)”辫樱。它不是線程峭拘,但它和線程息息相關(guān)。一般來(lái)講狮暑,一個(gè)線程一次...
    WeiHing閱讀 8,137評(píng)論 11 111
  • http://www.cocoachina.com/ios/20150601/11970.html RunLoop...
    紫色冰雨閱讀 837評(píng)論 0 3
  • 原文地址:http://blog.ibireme.com/2015/05/18/runloop/ RunLoop ...
    大餅炒雞蛋閱讀 1,157評(píng)論 0 6
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技術(shù) RunLoop 是 iOS 和 ...
    橙娃閱讀 854評(píng)論 1 2
  • 青澀時(shí)光我輕輕敲了你的窗心海波光粼粼臉上霞光 校園陽(yáng)光小鳥(niǎo)在吱吱喳喳象在訴說(shuō)我的秘密 于是口齒不清向你借了《窗外》...
    蔣光頭jL94430閱讀 748評(píng)論 38 87