Chapter 5. Memory Management
<br />
Item 32: Beware of Memory Management with Exception-Safe Code
<br />
這一節(jié)講的是使用exception時存在的內存管理問題惊搏。
在Item21講NSError的使用時已經提到了宝恶,OC的設計并不是exception-safe的,因此不能普遍地使用exception,而只應該在發(fā)生嚴重問題的時候才用礁叔。大多數(shù)情況使用NSError是更好地選擇。
具體來說俗或,在手動管理引用計數(shù)的時候风罩,避免內存泄露的寫法是這樣的:
EOCSomeClass *object;
@try {
object = [[EOCSomeClass alloc] init];
[object doSomethingThatMayThrow];
}
@catch (…) {
NSLog(@“error occurred”);
}
@finally {
[object release];
}
關鍵在于要添加一個@finally塊,這是一個不管是否拋出異常都會到達的塊犀填,所以把release放在這里蠢壹,保證object的釋放。如果沒有這個塊九巡,把release放在@try里图贸,那么如果釋放前拋出了異常,釋放這一句就不會被調用冕广,內存泄露就會產生疏日。
如果在ARC環(huán)境,release就不能手動調用了撒汉。但需要注意的是沟优,和普通情況不同,此時系統(tǒng)默認情況下也不會自動幫我們調用睬辐,必須開啟-fobjc-arc-exceptions編譯器標志以后挠阁,ARC才可以生成這種情況下的附加代碼。但是會一定程度影響運行效率溯饵。
<br />
Item 33: Use Weak References to Avoid Retain Cycles
<br />
這一節(jié)講用弱引用打破保留環(huán)侵俗。
篇幅比較短,內容我也比較熟悉丰刊,好像沒有什么特別要記下來的坡慌。實際中最常見的場景就是用block的時候要小心,什么時候需要定義weakSelf藻三。如果block直接對self引用比較容易看出來洪橘,但是環(huán)里有兩個以上的對象的時候就不是很容易看出來跪者,比如block里引用了一個已經引用self的對象。
__unsafe_unretained和__weak都能有打破環(huán)的效果熄求,因為都并不保留對象渣玲,但是因為存在野指針的問題,__weak是更推薦的做法弟晚,更安全忘衍。
<br />
Item 34: User Autorelease Pool Blocks to Reduce High-Memory Waterline
<br />
這一節(jié)講通過新建autorelease pool來壓低內存的峰值。
“The high-memory waterline refers to the highest memory footprint of an application during a certain period.” 一開始不太懂這個waterline到底指的什么卿城,根據這句理解是程序運行中的內存峰值枚钓。
如果對對象調用autorelease而不是release,就會使對象多存活一段時間瑟押,到下一個event loop才會調用一次release搀捷,這里是指只是用系統(tǒng)自帶的那個寫在main函數(shù)的自動釋放池的情況。但是如果所有的對象都等到這時再釋放多望,內存就會一直積累嫩舟,峰值就會很高。為了避免這種情況怀偷,當出現(xiàn)占用內存多但是又可以用完就釋放的對象時家厌,可以手動創(chuàng)建一個自動釋放池來使它提前釋放,比如這樣:
NSArray *databaseRecords = /*…*/
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
@autoreleasepool {
EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
[people addObject:person];
}
}
這個場景假設的是需要新建的person對象非常多椎工。如果不采用自動釋放池饭于,這些新建的person對象就會一直存在到下一個event loop,而采用這種寫法以后维蒙,在這個自動釋放池的末尾這些臨時對象就可以被回收了掰吕,這樣內存峰值就及時降下來了。
<br />
Item 35: Use Zombies to Help Debug Memory-Management Problems
<br />
這一節(jié)講的是用Zombie Objects來調試程序木西。
這個東西我不太熟悉畴栖,也沒有見過随静。不過原理還是比較好理解八千。對象在被釋放以后,原來存放對象的那塊內存會被標記為“可重用”燎猛,這樣就可以重新被分配做他用了恋捆。但是這個過程不是立即執(zhí)行的,具體什么時候執(zhí)行并不能確定重绷。所以這塊內存可能仍有東西在占用沸停,如果給它發(fā)消息有時甚至還能相應,但是不一定是我們希望的那個對象的響應昭卓,這樣就會有奇怪的Bug出現(xiàn)愤钾。而比較理想的情況是瘟滨,一旦內存被釋放了,不管有沒有被重新用掉能颁,我們都能得知這一信息杂瘸,如果給它發(fā)消息,能提示我們是在給已經釋放的對象發(fā)消息伙菊,利于我們調試败玉。
Zombie Object就能實現(xiàn)這一功能。默認是不打開NSZombieEnabled環(huán)境變量的镜硕,怎么打開文中寫了运翼,我找了一下,真的有誒←_←兴枯。
僵尸對象的原理是血淌,在NSObject執(zhí)行dealloc時,如果發(fā)現(xiàn)打開了NSZombieEnabled環(huán)境變量念恍,就通過method swizzling(Item13有講過)把dealloc的代碼變了六剥,多加了一部分,不僅把對象所屬的類變成NSZombie了峰伙,而且還自動加上了原類名作為后綴疗疟,就像_NSZombie_OriginalClassName
這種形式。此時如果再向這個對象發(fā)送消息瞳氓,程序會判斷消息的接收者的類型策彤,如果是以NSZombie為前綴,就不會執(zhí)行這個消息匣摘,而是打印一條錯誤的信息出來店诗,提示“message sent to deallocated instance”,并且會顯示對象銷毀前的類名音榜。
使用Zombie Object是不會把內存釋放的庞瘸,只是把需要釋放的內存改了所屬類。但是只是在調試時會使用赠叼,所以不會引起實際的效率問題擦囊。
<br />
Item 36: Avoid Using retainCount
<br />
用了整整一節(jié)來嫌棄retainCount這個方法...
結論就是永遠都不要用。原因的話嘴办,其實這個方法的reference已經寫的比較明白了:
This method is of no value in debugging memory management issues. Because any number of framework objects may have retained an object in order to hold references to it, while at the same time autorelease pools may be holding any number of deferred releases on an object, it is very unlikely that you can get useful information from this method.
總的來說瞬场,就是因為引用計數(shù)是一個非常動態(tài)的過程,特別是有自動釋放池存在的情況下涧郊,計數(shù)的增減有實際意義贯被,而絕對值并沒有實際意義。不過文中給了更為詳細例子。比如有時系統(tǒng)會做優(yōu)化彤灶,對象在引用計數(shù)為1時就可能被回收看幼。再比如,單例對象的引用計數(shù)永遠都很大而且不變幌陕。再比如桌吃,某個調用方法內部可能自行保留或者釋放了對象,而外面看不出來苞轿,使得到的結果和想象得不一致茅诱。所以調用這個方法得不到任何有效的信息,結論就是搬卒,永遠不要用瑟俭。