天天看點

objective-C 的記憶體管理之-自動釋放池(autorelease pool)

如果一個對象的生命周期顯而易見,很容易就知道什麼時候該new一個對象,什麼時候不再需要使用,這種情況下,直接用手動的retain和release來判定其生死足矣。但是有些時候,想知道某個對象在什麼時候不再使用并不那麼容易。如果下面的代碼,看上去非常簡單:

Sample.h類接口部分

Sample.m 類實作部分

在main函數中調用

不知道您是否意識到這段代碼有記憶體洩漏問題,因為Sample.m的toString方法生成了一個NSString類的執行個體,但是main函數最後隻釋放了Sample的執行個體s,卻并未釋放這個NSString類的字元串執行個體!

要糾正這個錯誤,main函數應該改成:

這種隐藏的錯誤很容易發生,也容易被忽視。為此obj-c 引用了自動釋放池(autorelease pool),每次用xcode建立項目時,可能大家已經注意到了有類似下面的代碼模闆:

即:xcode為開發者寫的代碼外層包了一層NSAutoreleasePool。這個池(pool)類似資料結構中的堆棧(Stack),相當于一個容器,每次對象調用autorelease方法時(obj-c中的正式說法應該是:對象發送autorelease消息),對象的引用計數并不真正變化,而是向pool中添加一條記錄,記下對象的這種要求。最後當pool發送drain或release消息時,池中的所有對象的這種要求一一被執行(即:pool被銷毀前,會通知池中的所有對象,全部發送release消息真正将引用計數減少,如果對象之前有發送過autorelease消息)

下面看一下基本的使用,先給Sample添加一個屬性int型的flag(用于在銷毀時看到是哪一個執行個體正在被銷毀),同時重寫dealloc()以便在釋放時能輸出一些資訊

Sample.h

Sample.m

使用自動釋放池後的main函數

運作結果為:

2011-02-24 13:28:09.759 MemoryManage[282:a0f] Sample 2 is going to die.

2011-02-24 13:28:09.769 MemoryManage[282:a0f] Sample 1 is going to die.

從結果上看,pool是後進先出的,即:最後autorelease的最先釋放(符合stack資料結構的特征)。再回到前面提到的toString方法中記憶體洩漏的問題,明白pool的基本原理後,隻要把return str換成retrun [str autorelease]就行了,即把該字元串在池中登記,這樣當[pool drain]時,所有登記的對象,将自動調用release方法,進而得到釋放。

自動釋放池從功能上可以了解為一種延時釋放技術:即通過發送autorelease消息,向自動釋放池登記,表明自己将來會在pool銷毀時,一并發送release消息銷毀自己。

最後提幾點注意事項:

1、NSAutoreleasePool的執行個體pool本身也是一個對象,同樣需要釋放,是以最後也同樣需要[pool release]或[pool drain],也正是這一行代碼,才會将池中的所有對象同時釋放。--注:drain僅适用于max os高版本,低版本不适用,而release通用,其它并無太大差别

2、pool在release時,僅僅隻是簡單的讓所有池中的對象都發送release而已,并無其它玄機。(即:讓池中所有對象的引用計數減1) 是以,如果你在之前用代碼強制retain了某對象的引用計數,即使pool被release了,池中的對象仍然有可能因為引用計數仍大于1,而未被銷毀。比如下面的代碼:

運作結果:

2011-02-24 13:49:22.558 MemoryManage[461:a0f] s1.retainCount=1,s2.retainCount=1

2011-02-24 13:49:22.566 MemoryManage[461:a0f] s1.retainCount=1,s2.retainCount=1

2011-02-24 13:49:22.567 MemoryManage[461:a0f] s1.retainCount=1,s2.retainCount=2

2011-02-24 13:49:22.578 MemoryManage[461:a0f] Sample 1 is going to die.

因為s2發送了retain消息,進而讓其引用計數上升為2,是以最終即使pool釋放後,s2仍未銷毀。

3、在iphone/ipad等記憶體有限的手持裝置上,并不建議使用autorelease,因為說到底這是一種延時釋放,如果你的程式一直在跑,代碼尚未執行到[pool release]之前,即使有很多對象不再需要了,但它們占用的記憶體并未真正釋放。

4、不要把大量循環操作放到同一個NSAutoreleasePool之間,道理同上,這樣會使池中有大量對象,導緻程式在運作時占用較多記憶體。比如下面這段代碼:

上面的代碼運作時,必須等整個循環結束後才開始銷毀對象。可以改進為下面這樣:

這樣每當池子裡有100個對象時,就釋放一次,這樣程式在運作時占用的記憶體就會少很多

最後從書上抄一段号稱Cocoa記憶體管理的黃金定律:如果我使用了new、alloc或copy方法獲得一個對象,則我必須釋放(release)或自動釋放(autorelease)該對象

繼續閱讀