盡管內(nèi)存管理策略( Memory Management Policy)中描述的基本概念比較簡單世杀,但還是有一些實際步驟可簡化內(nèi)存管理,并幫助你確保應(yīng)用是可靠的、健壯的,同時最大限度的減少其資源需求厘托。
使用訪問器方法來簡化內(nèi)存管理
如果類有個屬性是對象,你必須確保在使用它的過程中未被釋放稿湿。因此你必須在set時聲明該對象的所有權(quán)铅匹。你還必須確保之后放棄當(dāng)前持有值的所有權(quán)。
有時候看起來乏味或者迂腐饺藤,但如果一致使用訪問器方法包斑,與內(nèi)存管理相關(guān)的問題可能性大大降低。如果你在代碼中retain
和release
實例變量涕俗,那么肯定你做了件錯事舰始。
考慮你想set的計數(shù)器對象
<pre><code>
@interface Counter : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;
</pre></code>
property聲明兩個訪問器方法。通常情況下咽袜,你應(yīng)該訪問編譯器合成該方法,然而枕稀,看看他們是如何實現(xiàn)的是有益的询刹。
在get訪問器中,返回合成實例變量萎坷,所以沒有必要retain
或release
凹联。
<pre><code>
-(NSNumber *)count {
return _count;
}
</pre></code>
在set方法中,如果每個人按照相同的規(guī)則:假設(shè)新計數(shù)可能隨時被處理哆档,必須通過發(fā)送retain
消息獲取對象的所有權(quán)蔽挠,確保新計數(shù)不被處理。通過發(fā)送release
消息,放棄舊計數(shù)對象的所有權(quán)澳淑。(發(fā)送消息到nil在Objective-C是允許的比原,所以實現(xiàn)仍然正常即便_count
尚未設(shè)置。)你必須在 [newCount retain]
后發(fā)送杠巡,防止兩者是相同的對象——你不想無意中使它被回收量窘。
<pre><code>
-(void)setCount:(NSNumber *)newCount {
[newCount retain];
[_count release];
// Make the new assignment.
_count = newCount;
}
</pre></code>
使用訪問器方法設(shè)置屬性值
假設(shè)你想實現(xiàn)一個重置計數(shù)器的方法。你有兩種選擇氢拥。第一種實現(xiàn)使用alloc
創(chuàng)建NSNumber
實例蚌铜,所以使用release
釋放。
<pre><code>
-(void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[self setCount:zero];
[zero release];
}
</pre></code>
第二種使用一個方便的構(gòu)造函數(shù)創(chuàng)建一個新NSNumber
對象嫩海。因此不需要retain
或release
消息冬殃。
<pre><code>
-(void)reset {
NSNumber *zero = [NSNumber numberWithInteger:0];
[self setCount:zero];
}
</pre></code>
注意,兩種方法都是用訪問器方法叁怪。
以下代碼在簡單情況下肯定能正常工作审葬,但有可能避開訪問器方法,這樣做在某個階段會導(dǎo)致錯誤(例如骂束,當(dāng)您忘記retain或release耳璧,或者實例變量的內(nèi)存管理語義發(fā)生變化)。
<pre><code>
-(void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[_count release];
_count = zero;
}
</pre></code>
還要注意使用KVO展箱,這種改變變量的方式不兼容KVO旨枯。
不在初始化方法和dealloc中使用訪問器方法
在初始化方法和dealloc
不應(yīng)該使用訪問器方法設(shè)置實例變量。使用代表0 的number對象初始化計數(shù)器對象混驰,可以如下實現(xiàn)init方法:
<pre><code>
-init {
self = [super init];
if (self) {
_count = [[NSNumber alloc] initWithInteger:0];
}
return self;
}
</pre></code>
初始化計數(shù)器的計數(shù)不為0攀隔,可以如下實現(xiàn)initWithCount:
方法。
<pre><code>
-initWithCount:(NSNumber *)startingCount {
self = [super init];
if (self) {
_count = [startingCount copy];
}
return self;
}
</pre></code>
由于計數(shù)器對象有實例變量栖榨,還必須實現(xiàn)dealloc方法昆汹。通過發(fā)送release消息放棄所有實例變量的所有權(quán),并最終調(diào)用super的實現(xiàn):
<pre><code>
-(void)dealloc {
[_count release];
[super dealloc];
}
</pre></code>
使用弱引用避免循環(huán)引用
創(chuàng)建對象的一個強引用可以保留對象婴栽。一個對象不能被回收直到所有的強引用都被是否满粗。如果兩個對象有循環(huán)引用將導(dǎo)致循環(huán)引用問題,他們彼此有一個強引用(直接強引用或通過一連串其他對象的強引用會到第一個對象)愚争。
圖1所示對象關(guān)系說明了一個潛在的循環(huán)引用映皆。每個Document對象中的每個頁面有個Page對象。每個Page對象有一個屬性跟蹤它所在的文檔轰枝。如果Document對象有Page對象的強引用捅彻,而Page對象對Document對象也有強引用,則兩個對象都不能 被釋放鞍陨。Document的引用計數(shù)器不能變成0直到Page對象被釋放步淹,同時Page對象不能被釋放直到Document對象被回收。
解決循環(huán)引用問題可以使用弱引用。弱引用不擁有源對象的關(guān)系缭裆,當(dāng)有引用時不retain對象键闺。
為了保持對象圖的完整,然而幼驶,必須在某處有強引用(如果只有弱引用艾杏,那么頁面和段落可能沒有所有者,因此會被回收)盅藻。Cocoa建立一個慣例购桑,因此,一個“父”對象必須對其“zi”對象保持強引用氏淑,“子”對象必須對“父”對象是弱引用勃蜘。
所以,在圖1中假残,Document對象有一個強引用(retain)其Page對象缭贡,但Page對象有一個弱引用(不retain)Document對象。
Cocoa中弱引用的例子包括但不限于表數(shù)據(jù)源辉懒,大綱視圖阳惹,通知觀察者,其他目標(biāo)和代理眶俩。
當(dāng)你只有弱引用對象時莹汤,發(fā)送消息要小心。如果你在對象被回收之后發(fā)送消息颠印,app會崩潰纲岭。當(dāng)對象可用時必須有明確的條件。在大多數(shù)情況下线罕,弱引用對象知道其他對象的弱引用止潮,為了防止循環(huán)引用,當(dāng)被回收時負(fù)責(zé)通知其他對象钞楼。例如喇闸,當(dāng)你注冊通知中心對象,通知中心存儲該對象的弱引用并在特定通知發(fā)布后發(fā)送消息询件。當(dāng)對象被回收燃乍,需要在通知中心注銷防止通知中心發(fā)送消息到該對象。同樣的雳殊,當(dāng)代理對象被回收,通過發(fā)送nil參數(shù)的setDelegate:
消息到其他對象刪除該代理窗轩。這些消息通常從對象的dealloc方法中發(fā)送夯秃。
避免回收正在使用的對象
Cocoa的所有權(quán)策略通常指接收對象應(yīng)該在整個調(diào)用過程中保持有效。它也有可能返回一個當(dāng)前范圍內(nèi)不會被釋放的接收對象。它不能影響app對象的getter方法返回一個緩存實例變量或一個計算值仓洼。重要的是該對象在你需要使用的時候仍然是有效的介陶。
偶爾有例外,主要是這兩種情況色建。
1.當(dāng)一個對象從基本collection classes中刪除哺呜。
<pre><code>
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.
</pre></code>
當(dāng)一個對象從基本集合類中刪除,發(fā)送一個release(而不是autorelease)消息箕戳。如果集合是刪除對象的唯一所有者某残,刪除對象(在例子中的`heisenObject`)立馬被回收。
2.當(dāng)“父對象”被回收陵吸。
<pre><code>
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.
</pre></code>
在某些情況下玻墅,從另一個對象中檢索一個對象,然后直接或間接釋放父對象壮虫。如果釋放父對象導(dǎo)致其被回收澳厢,并且父對象是子對象的唯一所有者,則子對象(例子中的heisenObject
)會同時被回收(假設(shè)在父對象的dealloc方法中發(fā)送一個release而不是autorelease消息)囚似。
為了防止這些情況剩拢,在接收到heisenObject
時retain,在完成時饶唤,release徐伐。例如:
<pre><code>
heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];
</pre></code>
不要使用dealloc管理稀缺資源
通常不需要在dealloc方法中管理稀缺資源如文件描述符、網(wǎng)絡(luò)連接搬素、緩存或緩存呵晨。特別是,不要設(shè)計在dealloc中會調(diào)用的類熬尺。dealloc的調(diào)用可能因為一個錯誤或應(yīng)用中斷被推遲或回避摸屠。
相反,如果你有一個類粱哼,它的實例管理稀缺資源時季二,應(yīng)該設(shè)計應(yīng)用,在你不再需要資源時可以告訴實例需要清理揭措。通常釋放該實例胯舷,然后dealloc,如果沒有dealloc绊含,你也不會遇到其他問題桑嘶。
如果你嘗試在dealloc中管理資源可能出現(xiàn)問題。例如:
順序依賴對象圖銷毀
對象圖銷毀本質(zhì)上是無序的躬充。盡管通常你預(yù)期得到一個特定的屬性逃顶,你的引用是脆弱的讨便。如果對象意外自動釋放而不是釋放,則釋放順序可能改變以政,這可能導(dǎo)致意想不到的結(jié)果霸褒。
不回收稀缺資源
內(nèi)存泄露是bug必須修復(fù),但他們通常不會立即死亡盈蛮。如果稀缺資源不立即釋放废菱,然而,你會遇到更嚴(yán)重的問題抖誉。如果應(yīng)用耗盡了文件描述符殊轴,例如,用戶可能無法保存數(shù)據(jù)寸五。
在錯誤的線程上執(zhí)行邏輯清理梳凛。
如果對象在意想不到的時間被自動釋放,在它所在的線程自動釋放池block中被回收梳杏。這種只能從一個線程訪問的資源是致命的韧拒。
集合擁有他們包含的對象
當(dāng)你往集合(例如array、dictionary或set)中添加對象十性,集合會擁有該對象的所有權(quán)叛溢。當(dāng)該對象從集合中刪除或集合本身被釋放時,集合會放棄所有權(quán)劲适。因此楷掉,例如,如果你想創(chuàng)建數(shù)字?jǐn)?shù)組霞势,你可以按照以下實現(xiàn):
<pre><code>
NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
[array addObject:convenienceNumber];
}
</pre></code>
在這種情況下烹植,你沒有調(diào)用alloc,所以沒有必要調(diào)用release愕贡。也沒有必要retain新數(shù)據(jù)(convenienceNumber
)草雕,因為數(shù)組將釋放。
<pre><code>
NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
[array addObject:allocedNumber];
[allocedNumber release];
}
</pre></code>
在這種情況固以,你需要在for循環(huán)的范圍內(nèi)發(fā)送release消息以此來抵消alloc墩虹。因為通過addObject:
往數(shù)組中添加對象時,數(shù)組retain該對象憨琳,該對象只要在數(shù)組中就不會被回收诫钓。
要理解這點,把自己放到實現(xiàn)集合類的人的位置篙螟。你想確保不需要提供對象菌湃,所以當(dāng)他們被傳入時,向他們發(fā)送retain消息遍略。如果他們被刪除惧所,你必須發(fā)送release消息场梆,在dealloc方法中給其他保留的對象發(fā)送release消息。
使用Retain Counts實現(xiàn)所有權(quán)策略
所有權(quán)策略是通過引用計數(shù)實現(xiàn)纯路,通常稱為“retain count”。每個對象都有retain count寞忿。
當(dāng)你創(chuàng)建一個對象驰唬,它的retain count為1.
當(dāng)你給對象發(fā)送retain消息,它的retain count增加1.
當(dāng)你給對象發(fā)送release消息腔彰,它的retain count減1.
當(dāng)你給對象發(fā)送autorelease消息叫编,它的retain count在當(dāng)前自動釋放池block的最后減1。
如果對象的retain count減到0霹抛,它被回收搓逾。
重要:沒有明確理由訪問一個對象的retain count(參見
retainCount
)。結(jié)果往往是誤導(dǎo)杯拐,因為你可能沒有意識到礦建對象retain你要的對象霞篡。在調(diào)試內(nèi)存管理問題,你應(yīng)該只關(guān)心你的代碼是否堅持所有權(quán)規(guī)則端逼。