前言
在MRC下, 我們需要手動管理內存, 寫一大堆的retain, release代碼, 稍不留神就會造成內存泄露; 而ARC下, 編譯器幫我們屏蔽掉了這些繁瑣的代碼, 我們不需要再一條一條地寫retain, release了, 可以專心地把精力放在業務邏輯, 技術上.
在MRC下, 調用[object autorelease]可以延遲對象的內存釋放; 在ARC下, 我們甚至可以不需要知道 autorelease 是什么都能管理好內存. 編譯器幫我們做了什么事情? 到底 autorelease 有什么神奇的地方? autorelease pool 又是個什么東西?下面我將會一一道來.
NSAutoreleasePool 與 @autoreleasepool
NSAutoreleasePool 是 Cocoa 用來支持引用計數內存管理機制的類, 當一個autorelease pool(自動釋放池)被drain(銷毀)的時候會對pool里的對象發送一條release的消息.
注意 : 在ARC下, 不能使用NSAutoreleasePool這個類來創建自動釋放池, 而應該用@autoreleasepool { } 這個block, 官方文檔源碼如下 :
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release];
用以下代碼代替上述代碼 :
@autoreleasepool {
// Code benefitting from a local autorelease pool.
}
Ps : 官方文檔說明, 使用@autoreleasepool這個block比NSAutoreleasePool更高效!并且在MRC環境下同樣適用*
讓我們用一張圖片來了解對象調用autorelease的整個過程
補充幾點 :
- 蘋果官方文檔說
An object can be put into the same pool several times, in which case it receives a release message for each time it was put into the pool.
意思是一個對象可以多次放入同一池子中, 并且每次放進去的時候都會調用release方法.. 但是我在MRC下做了測試, 結果是每調用一次autorelease方法, 池子中就多一個對象. 調用5次之后打印池子, 發現池子中有5個相同的對象.... 有知道的朋友麻煩告訴下謝謝 -
程序中至少存在一個自動釋放池, 否則autoreleased對象將不能對應收到release消息而導致內存泄露.
自動釋放池 - NSAutoreleasePool對象不能retain, 不能autorelease, 所以drain方法(或者release方法, 但是這兩者有所不同, 下文會說)可以直接釋放內存. 你應該在同一個上下文(調用創建這個池的同一個方法, 函數或者循環體)中drain一個自動釋放池.
- MRC下需要對象調用autorelease才會入池, ARC下可以通過
__autoreleasing
修飾符, 否則的話看方法名, 非alloc/new/copy/mutableCopy開頭的方法編譯器都會自動幫我們調用autorelease方法. - 不一定要自己創建自動釋放池, 但是有3種情況下是很必要的, 下面會講.
- 自動釋放池可以嵌套使用
下面講autorelease pool 與 線程, RunLoop的關系...
autorelease pool 與 線程
每一個線程(包括主線程)都有一個NSAutoreleasePool棧. 當一個新的池子被創建的時候, push進棧. 當池子被釋放內存時, pop出棧. 對象調用autorelease方法進入棧頂的池子中. 當線程結束的時候, 它會自動地銷毀掉所有跟它有關聯的池子.
如果你的應用或者線程是長期存在的并且有可能產生大量的autoreleased對象, 你應該定期地drain和create自動釋放池, 否則, autorelease對象會在內存中堆積造成內存告急. 這里借用土土哥的一張圖,
測試的內容:500000次循環,每次循環創建一個NSNumber實例和兩個NSString實例。
圖:紅線表示沒有用@autoreleasepool時的內存占用。
圖:綠線表示用了@autoreleasepool優化后的內存占用!
如上圖所示, 在每一次循環中, 先創建一個自動釋放池, 然后循環結束的時候, 自動釋放池銷毀, release掉池中對象, 釋放內存. 對比之下, 優劣不言而喻.
蘋果也是這么做的, 數組的block遍歷方法就是, 大家可以自行測試.
autorelease pool 與 RunLoop
程序運行 -> 開啟事件循環 -> 發生觸摸事件 -> 創建自動釋放池 -> 處理觸摸事件 -> 事件對象加入自動釋放池 -> 一次事件循環結束, 銷毀自動釋放池.
蘋果官方文檔說 :
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event
在開始每一個事件循環之前系統會在主線程創建一個自動釋放池, 并且在事件循環結束的時候把前面創建的釋放池釋放, 回收內存. 這里不深入講解RunLoop, 文章后面會給出幾篇RunLoop的文章, 大家可以去看看.
如何管理自動釋放池
這里介紹4個方法
- release
- drain
- autorelease
- retain
release 和 drain
這里把他們放在一塊講, 是因為他們在引用計數環境下都能銷毀一個自動釋放池, 為什么這里要特意說明引用計數環境, 因為在引用計數環境和垃圾回收(GC)環境下, 這兩個方法不盡相同
在引用計數環境下, release 和 drain 效果相同, 均能銷毀一個自動釋放池.
在垃圾回收環境下, drain同上, release 則是一個空方法.
所以建議為了兼容性, 統一用drain吧.
autorelease 和 retain
拋出異常, 因為NSAutoreleasePool不能調用以上 autorelease 和 retain 方法.
怎么使用autorelease pool
由于@autoreleasepool同時兼容MRC和ARC編譯環境(NSAutoreleasePool只能在MRC下使用), 所以以下均是以autorelease pool block來介紹使用.
Cocoa 希望代碼總是在autorelease pool block中被執行, 否則autoreleased對象就得不到釋放從而造成內存泄露.
什么時候需要自己手動創建autorelease pool
看蘋果官方文檔怎么說明 :
If you are writing a program that is not based on a UI framework, such as a command-line tool.
If you write a loop that creates many temporary objects.
You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.If you spawn a secondary thread.
You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects.你寫的程序不是基于UI framework, 例如命令行項目
你寫的循環創建了大量臨時對象 -> 你需要在循環體內創建一個autorelease pool block并且在每次循環結束之前處理那些autoreleased對象. 在循環中使用autorelease pool block可以降低內存峰值
你創建了一個新線程
當線程開始執行的時候你必須立馬創建一個autorelease pool block, 否則你的應用會造成內存泄露.
使用場景 :
- 利用@autoreleasepool優化循環, 如上述提過的例子所示
- 如果你的應用程序或者線程是要長期運行的并且有可能產生大量autoreleased對象, 你應該使用autorelease pool blocks
- 長期在后臺中運行的任務, 方法
使用方法 : 不要太簡單~~
@autoreleasepool {
// Code here
}
這里介紹一種特殊的情況
先上蘋果官方源碼
– (id)findMatchingObject:(id)anObject {
id match;
while (match == nil) {
@autoreleasepool {
// Do a search that creates a lot of temporary objects.
match = [self expensiveSearchForObject:anObject];
if (match != nil) {
[match retain]; /* Keep match around. */
}
}
}
return [match autorelease]; /* Let match go and return it. */
}
在block結束之后, 你要注意的是任何autoreleased對象已經被處理過了(release). 請不要對這個對象發送消息或者把這個對象當做方法的返回值返回. 會引發野指針錯誤.
解決方法 : 蘋果是這么做的 : 在block內對match對象發送retain消息和在block外對match發送autorelease消息能延長match對象的生命周期并且允許match對象在block外部接收消息或者作為方法的返回值返回. 我們不需要再關心match什么時候釋放, 因為它已經交給了上一層的autorelease pool去管理.
參考文檔 :
NSAutoreleasePool Class Reference
Using Autorelease Pool Blocks
黑幕背后的Autorelease
@autoreleasepool-內存的分配與釋放
這里推薦兩個RunLoop的文章
深入理解RunLoop
RunLoop
歡迎大家關注@Jerry4me, 同時本文有錯漏的點懇請大家不吝指出, 謝謝~