關(guān)于自動釋放池@autoreleasepool你需要知道知識

文章也同時在個人博客 http://kimihe.com/更新

引言

OC對象的生命周期取決于引用計數(shù),我們有兩種方式可以釋放對象:一種是直接調(diào)用release釋放;另一種是調(diào)用autorelease將對象加入自動釋放池中。自動釋放池用于存放那些需要在稍后某個時刻釋放的對象。

本文將介紹自動釋放池的原理和使用場景,并結(jié)合一道據(jù)說是優(yōu)酷iOS的筆試題來舉例說明自動釋放池的妙用。

更多關(guān)于iOS內(nèi)存管理的文章已經(jīng)匯總至:深入總結(jié)iOS內(nèi)存管理。

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

如果沒有自動釋放池而給對象發(fā)送autorelease消息,將會收到控制臺報錯。但一般我們無需擔(dān)心自動釋放池的創(chuàng)建問題。

我們的Mac以及iOS系統(tǒng)會自動創(chuàng)建一些線程,例如主線程和GCD中的線程,都默認(rèn)擁有自動釋放池。每次執(zhí)行 “事件循環(huán)”(event loop)時,就會將其清空,這一點(diǎn)非常重要,請務(wù)必牢記! 關(guān)于事件循環(huán),其涉及到runloop,可以看這篇文章:深入理解RunLoop。

因此我們一般不需要手動創(chuàng)建自動釋放池,通常只有一個地方需要它,那就是在main()函數(shù)里,如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

這個main()函數(shù)里面的池并非必需。因為塊的末尾是應(yīng)用程序的終止處,即便沒有這個自動釋放池,也會由操作系統(tǒng)來釋放。但是這些由UIApplicationMain函數(shù)所自動釋放的對象就沒有池可以容納了,系統(tǒng)會發(fā)出警告。因此,這里的池可以理解成最外圍捕捉全部自動釋放對象所用的池。

@autoreleasepool的作用

大家可以先看一下下面的iOS筆試題的第5題(修改代碼的錯誤),如下圖:


優(yōu)酷iOS筆試題

這段代碼問題在哪里呢?題目的解答請繼續(xù)閱讀。筆者先給一個提示:與內(nèi)存的釋放有關(guān)。

現(xiàn)考慮如下代碼:

for (int i = 0; i < 10000; i++) {
    [self doSthWith:object];
}

這段代碼和筆試題關(guān)鍵部分大同小異。如果"doSthWith:"方法要創(chuàng)建一個臨時對象,那么這個對象很可能會放在自動釋放池里。筆試題中最后stringByAppendingString方法很有可能屬于上述的方法。因此如果涉及到了自動釋放池,那么問題也應(yīng)該就出在上面。

注意:即便臨時對象在調(diào)用完方法后就不再使用了,它們也依然處于存活狀態(tài),因為目前它們都在自動釋放池里,等待系統(tǒng)稍后進(jìn)行回收。但自動釋放池卻要等到該線程執(zhí)行下一次事件循環(huán)時才會清空,這就意味著在執(zhí)行for循環(huán)時,會有持續(xù)不斷的新的臨時對象被創(chuàng)建出來,并加入自動釋放池。要等到結(jié)束for循環(huán)才會釋放。在for循環(huán)中內(nèi)存用量會持續(xù)上漲,而等到結(jié)束循環(huán)后,內(nèi)存用量又會突然下降。

而如果把循環(huán)內(nèi)的代碼包裹在“自動釋放池”中,那么在循環(huán)中自動釋放的對象就會放在這個池,而不是在線程的主池里面。如下:

for (int i = 0; i < 1000000; i++) {
        @autoreleasepool {
            NSString *str = @"abc";
            str = [str lowercaseString];
            str = [str stringByAppendingString:@"xyz"];
        }        
}

新增的自動釋放池可以減少內(nèi)存用量,因為系統(tǒng)會在塊的末尾把這些對象回收掉。而上述這些臨時對象,正在回收之列。

自動釋放池的機(jī)制就像“棧”。系統(tǒng)創(chuàng)建好池之后,將其壓入棧中,而清空自動釋放池相當(dāng)于將池從棧中彈出。在對象上執(zhí)行自動釋放操作,就等于將其放入位于棧頂?shù)哪莻€池。

實(shí)驗驗證

我們可以通過實(shí)驗進(jìn)行驗證。新建工程加入上述代碼,并關(guān)閉ARC(不然是看不到區(qū)別的)。

在未添加autoreleasepool時,我們的堆內(nèi)存實(shí)時分配情況如下圖:

HeapWithoutAutoreleasepool

大家可以看到Persistent Bytes不斷增加,到達(dá)100W次的創(chuàng)建峰值后(出for循環(huán))開始逐步釋放。因此圖像是一個向上凸的曲線。

在加入autoreleasepool后,我們看到如下的曲線:

HeapWithAutoreleasepool

可以發(fā)現(xiàn)盡管字符串在不斷地創(chuàng)建,但由于得到了及時的釋放,堆內(nèi)存始終保持在一個很低的水平。

其他注意點(diǎn)

@autoreleasepool語法還有一個好處,就是可以避免無意間誤用那些在清空池之后已被系統(tǒng)回收的對象,例如:

@autoreleasepool {
    id obj = [self createObject];
}
[self useObject:obj];

上述代碼在編譯時就會基于錯誤警告,因為obj出了自動釋放池就不可用了。

@autoreleasepool小結(jié)

  • 自動釋放池排布在棧中,對象受到autorelease消息后,系統(tǒng)將其放入棧頂?shù)某乩铩?/li>
  • 合理運(yùn)用自動釋放池,可以降低程序的內(nèi)存峰值。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,917評論 0 11
  • 29.理解引用計數(shù) Objective-C語言使用引用計數(shù)來管理內(nèi)存,也就是說,每個對象都有個可以遞增或遞減的計數(shù)...
    Code_Ninja閱讀 1,754評論 1 3
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,656評論 30 472
  • 一、官網(wǎng)關(guān)于自動釋放池的說明截取 NSAutoreleasePool NSAutoreleasePool 類被用來...
    Mitchell閱讀 14,133評論 5 40
  • 11.看下面的程序,第一個NSLog會輸出什么?這時str的retainCount是多少?第二個和第三個呢? 為什...
    AlanGe閱讀 846評論 1 4

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