1.簡(jiǎn)介
應(yīng)用程序的內(nèi)存管理就是在程序的運(yùn)行期開(kāi)辟內(nèi)存,使用內(nèi)存织阳,使用完畢后釋放內(nèi)存眶蕉。一個(gè)好的程序使用盡可能少的內(nèi)存。在Objective-C中唧躲,內(nèi)存管理可以認(rèn)為是一種分配有限內(nèi)存資源的所有權(quán)給多種數(shù)據(jù)和代碼的方式造挽。當(dāng)你讀完這篇文章時(shí),你應(yīng)該具備管理你程序內(nèi)存的知識(shí)弄痹,明確地管理對(duì)象的生命周期并在對(duì)象不在需要時(shí)釋放它們饭入。
盡管內(nèi)存管理通常只在個(gè)別對(duì)象的層次考慮,但是我們的目標(biāo)是管理對(duì)象圖表肛真。你應(yīng)該確保內(nèi)存中不會(huì)有實(shí)際不需要的對(duì)象谐丢。
- 對(duì)象圖表
概覽
Objective-C提供了兩種方式管理內(nèi)存
- 本篇文章用到的方法,被稱(chēng)為“手動(dòng)持有-釋放”蚓让,英文為“manual retain-release”乾忱,或者叫MRR,通過(guò)追蹤你持有的對(duì)象來(lái)明確地管理內(nèi)存凭疮。它通過(guò)一種由
NSObject
提供的叫做引用計(jì)數(shù)的模型與運(yùn)行時(shí)系統(tǒng)一同來(lái)實(shí)現(xiàn)。 - 在自動(dòng)引用計(jì)數(shù)(ARC)中串述,系統(tǒng)像MRR一樣使用同樣的引用計(jì)數(shù)系統(tǒng)执解,只不過(guò)ARC在編譯階段幫你插入了合適的內(nèi)存管理方法調(diào)用。雖然現(xiàn)在很少使用MRR,但是了解MRR在某些情況下還是很有用的衰腌。
好的習(xí)慣可以避免內(nèi)存相關(guān)問(wèn)題
錯(cuò)誤的內(nèi)存管理會(huì)導(dǎo)致兩類(lèi)主要的內(nèi)存問(wèn)題
- 釋放或者重寫(xiě)仍在使用的數(shù)據(jù)
這會(huì)導(dǎo)致內(nèi)存崩潰新蟆,通常會(huì)導(dǎo)致應(yīng)用崩潰,更糟的會(huì)丟失用戶(hù)數(shù)據(jù)右蕊。 - 不釋放不再使用的數(shù)據(jù)導(dǎo)致內(nèi)存泄漏
內(nèi)存泄漏就是開(kāi)辟的內(nèi)存不再使用時(shí)未被釋放琼稻。這會(huì)導(dǎo)致你的應(yīng)用使用持續(xù)增加的內(nèi)存,反過(guò)來(lái)會(huì)導(dǎo)致低下的系統(tǒng)性能或者應(yīng)用被終止饶囚。
但是帕翻,從引用計(jì)數(shù)的角度思考內(nèi)存管理通常達(dá)不到預(yù)期效果,因?yàn)槟阙呄蛴谝罁?jù)實(shí)現(xiàn)細(xì)節(jié)而不是實(shí)際目標(biāo)來(lái)思考內(nèi)存管理萝风。相反嘀掸,應(yīng)該從對(duì)象所有權(quán)和對(duì)象圖表的角度去思考內(nèi)存管理。
使用分析工具調(diào)試內(nèi)存問(wèn)題
想在編譯階段找出代碼的內(nèi)存問(wèn)題规惰,可以使用Clang的靜態(tài)分析器睬塌,快捷鍵是:command + shift + B。
如果內(nèi)存管理問(wèn)題依舊出現(xiàn)歇万,還有其它的工具和技術(shù)來(lái)幫助識(shí)別和診斷問(wèn)題所在揩晴。
- 許多工具和技術(shù)可以參考iOS Debugging Magic,尤其是使用
NSZombie
來(lái)查找過(guò)度釋放對(duì)象贪磺。 - 使用Instruments來(lái)追蹤引用計(jì)數(shù)事件硫兰,尋找內(nèi)存泄漏,見(jiàn)Collecting Data on Your App
2.內(nèi)存管理策略
在一個(gè)引用計(jì)數(shù)的環(huán)境下內(nèi)存管理的基礎(chǔ)模型是由定義在NSObject
協(xié)議的一組方法和一個(gè)標(biāo)準(zhǔn)的方法命名規(guī)則提供的缘挽。NSObject
類(lèi)也定義了一個(gè)dealloc
方法瞄崇,當(dāng)一個(gè)對(duì)象被銷(xiāo)毀(deallocated)時(shí)這個(gè)方法會(huì)自動(dòng)調(diào)起。本節(jié)描述了所有基本的規(guī)則壕曼,你需要了解這些規(guī)則來(lái)正確的管理Cocoa項(xiàng)目的內(nèi)存苏研,并且提供了一些正確使用內(nèi)存的例子。
2.1.基本的內(nèi)存管理規(guī)則
內(nèi)存管理模型是基于對(duì)象所有權(quán)的腮郊。一個(gè)對(duì)象也許有一個(gè)或多個(gè)所有者(owners)摹蘑。一個(gè)對(duì)象只要還擁有至少一個(gè)所有者,它就會(huì)繼續(xù)存活下去轧飞。如果沒(méi)有所有者衅鹿,運(yùn)行時(shí)系統(tǒng)會(huì)自動(dòng)銷(xiāo)毀這個(gè)對(duì)象。為了明確何時(shí)你擁有一個(gè)對(duì)象过咬,何時(shí)你不擁有一個(gè)對(duì)象大渤,Cocoa設(shè)置了以下策略:
-
你擁有任何你創(chuàng)建的對(duì)象
使用以“alloc”, “new”, “copy”, 或者 “mutableCopy”開(kāi)頭的方法創(chuàng)建一個(gè)對(duì)象(例如alloc
,newObject
或者mutableCopy
)掸绞。 -
使用retain可以獲得對(duì)象的所有權(quán)
一個(gè)接受的對(duì)象通常在方法內(nèi)是可以保證持續(xù)有效的泵三,方法可也以安全的將這個(gè)對(duì)象返回給它的調(diào)用者。在兩種情況下使用retain
:1.在獲取方法的實(shí)現(xiàn)或者init
方法來(lái)持有一個(gè)你想存儲(chǔ)為屬性的對(duì)象的所有權(quán);2.避免由于一些其它操作的副作用導(dǎo)致對(duì)象變得無(wú)效烫幕。 -
當(dāng)你不再需要一個(gè)對(duì)象時(shí)俺抽,你必須放棄你擁有的對(duì)象的所有權(quán)
通過(guò)向一個(gè)對(duì)象發(fā)送release
或者autorelease
消息來(lái)放棄這個(gè)對(duì)象的所有權(quán)。以Cocoa術(shù)語(yǔ)较曼,放棄一個(gè)對(duì)象的所有權(quán)通常被稱(chēng)為釋放(“releasing”)一個(gè)對(duì)象磷斧。 -
你不可以放棄一個(gè)你不擁有的對(duì)象的所有權(quán)
這只是以上策略的推論。
一個(gè)簡(jiǎn)單的例子
為了闡明以上策略捷犹,思考以下代碼片段:
Person *aPerson = [[Person alloc] init];
// ...
NSString *name = aPerson.fullName;
// ...
[aPerson release];
這個(gè)Person
對(duì)象是通過(guò)alloc
方法創(chuàng)建的弛饭,因此接下來(lái)當(dāng)它不再需要時(shí)收到了一條release
消息。但是注意伏恐,這個(gè)例子使用了release
而不是autorelease
孩哑。
使用autorelease發(fā)送一條延遲釋放消息
使用autorelease
來(lái)發(fā)送一條延遲釋放消息,通常用在從一個(gè)方法返回一個(gè)對(duì)象翠桦。例如横蜒,可以這樣實(shí)現(xiàn)fullName
方法:
- (NSString *)fullName {
NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
self.firstName, self.lastName] autorelease];
return string;
}
你擁有這個(gè)通過(guò)alloc
返回的字符串。為了遵守內(nèi)存管理規(guī)則销凑,你必須在失去對(duì)它的引用前放棄這個(gè)字符串的所有權(quán)丛晌。但是,如果使用release
斗幼,這個(gè)字符串會(huì)在被返回前就被銷(xiāo)毀(這個(gè)方法會(huì)返回一個(gè)無(wú)效的對(duì)象)澎蛛。使用autorelease
意味著你想要放棄所有權(quán),但是你允許方法的調(diào)用者在返回值被銷(xiāo)毀前使用它蜕窿。
也可以像下面這樣實(shí)現(xiàn)fullName
方法:
- (NSString *)fullName {
NSString *string = [NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName];
return string;
}
根據(jù)基礎(chǔ)規(guī)則谋逻,你不擁有通過(guò)stringWithFormat:
返回的字符串,所以可以安全的從這個(gè)方法返回這個(gè)字符串桐经。
相反毁兆,下面的實(shí)現(xiàn)就是錯(cuò)誤的:
- (NSString *)fullName {
NSString *string = [[NSString alloc] initWithFormat:@"%@ %@",
self.firstName, self.lastName];
return string;
}
根據(jù)命名規(guī)則,fullName
方法的調(diào)用者并不擁有返回的字符串阴挣。調(diào)用者因此沒(méi)有理由去釋放這個(gè)返回值气堕,進(jìn)而導(dǎo)致內(nèi)存泄漏。
你不擁有通過(guò)引用返回的對(duì)象
Cocoa的一些方法指定一個(gè)對(duì)象是通過(guò)引用返回的畔咧,也就是說(shuō)這個(gè)方法使用一個(gè)ClassName **
或者id *
類(lèi)型的參數(shù)茎芭。一個(gè)通用的模式是使用一個(gè)NSError
對(duì)象,它包含了錯(cuò)誤的信息誓沸。
例如NSData
的initWithContentsOfURL:options:error:
方法梅桩,或者NSString
的initWithContentsOfFile:encoding:error:
方法。
在這些情況下拜隧,同樣的內(nèi)存管理規(guī)則是適用的宿百。當(dāng)你喚起任何這樣的方法煮寡,你并沒(méi)有創(chuàng)建NSError
對(duì)象,因此你并不擁有它犀呼。因此也就沒(méi)必要去釋放它,像下面的例子這樣:
NSString *fileName = <#Get a file name#>;
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
// Deal with error...
}
// ...
[string release];
2.2.實(shí)現(xiàn)dealloc來(lái)放棄對(duì)象的所有權(quán)
NSObject
類(lèi)定義了一個(gè)dealloc
方法薇组,當(dāng)一個(gè)對(duì)象沒(méi)有所有者并且這個(gè)對(duì)象的內(nèi)存被回收時(shí)會(huì)自動(dòng)調(diào)起這個(gè)方法--用Cocoa的術(shù)語(yǔ)就是這個(gè)對(duì)象被釋放或者銷(xiāo)毀了外臂。dealloc
方法的角色是釋放這個(gè)對(duì)象自己的內(nèi)存,清除任何它持有的資源律胀,包括任何對(duì)象實(shí)例變量的所有權(quán)宋光。
下面的例子演示了如何為一個(gè)Person類(lèi)實(shí)現(xiàn)一個(gè)dealloc
方法:
@interface Person : NSObject
@property (retain) NSString *firstName;
@property (retain) NSString *lastName;
@property (assign, readonly) NSString *fullName;
@end
@implementation Person
// ...
- (void)dealloc
[_firstName release];
[_lastName release];
[super dealloc];
}
@end
重要:永遠(yuǎn)不要直接調(diào)用另一個(gè)對(duì)象的dealloc
方法。
必須在實(shí)現(xiàn)的最后調(diào)用父類(lèi)的實(shí)現(xiàn)炭菌。
當(dāng)程序終止時(shí)罪佳,對(duì)象也許不會(huì)收到dealloc
消息。因?yàn)樵谕顺鰰r(shí)進(jìn)程的內(nèi)存會(huì)自動(dòng)被清理黑低,讓操作系統(tǒng)來(lái)清理內(nèi)存要比調(diào)起所有的內(nèi)存管理方法高效的多赘艳。
2.3.Core Foundation適用相似但不同的規(guī)則
對(duì)于Core Foundation對(duì)象有相似的內(nèi)存管理規(guī)則,詳見(jiàn)Memory Management Programming Guide for Core Foundation克握。但是對(duì)于Cocoa和Core Foundation的命名規(guī)則是不同的蕾管。尤其是Core Foundation的創(chuàng)建規(guī)則(詳見(jiàn)The Create Rule)并不適用于返回Objective-C對(duì)象的方法。例如下面的代碼片段菩暗,你并不負(fù)責(zé)放棄myInstance
的所有權(quán):
MyClass *myInstance = [MyClass createInstance];
3.實(shí)用的內(nèi)存管理
盡管上面介紹的基礎(chǔ)概念非常簡(jiǎn)單,但是依然有一些實(shí)用的技巧可以讓管理內(nèi)存更加容易,確保你的程序在最小化內(nèi)存開(kāi)銷(xiāo)時(shí)仍然可靠黄虱,健壯袭艺。
3.1.使用存取方法讓內(nèi)存管理更加簡(jiǎn)單
如果你的類(lèi)有一個(gè)對(duì)象類(lèi)型的存取屬性,必須確保任何設(shè)置為這個(gè)值的對(duì)象在使用期間不會(huì)被銷(xiāo)毀佑稠。因此在設(shè)置這個(gè)對(duì)象時(shí)必須認(rèn)領(lǐng)其所有權(quán)秒梅。同樣必須確保稍后放棄當(dāng)前持有值的所有權(quán)。
有時(shí)這看起來(lái)有些冗長(zhǎng)和迂腐的讶坯,但是如果你持續(xù)使用存取方法番电,那么出現(xiàn)內(nèi)存管理錯(cuò)誤的概率會(huì)大大降低。如果你的代碼中的實(shí)例變量充斥著retain
和release
辆琅,那么實(shí)在是在做一件錯(cuò)事漱办。
假設(shè)一個(gè)計(jì)數(shù)器對(duì)象,你想設(shè)置它的值婉烟。
@interface Counter : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;
這個(gè)屬性聲明了兩個(gè)存取方法娩井。通常的你應(yīng)該請(qǐng)求編譯器來(lái)合成這兩個(gè)方法;但是如果了解它們是如何實(shí)現(xiàn)的是非常有意義的似袁。
在get
方法洞辣,只需要返回合成的實(shí)例變量咐刨,因此不需要retain
和release
:
- (NSNumber *)count {
return _count;
}
在set
方法,如果其它所有對(duì)象都遵循同樣的規(guī)則那么必須假設(shè)這個(gè)新值也許會(huì)在任何時(shí)候被丟棄扬霜,因此你必須持有這個(gè)對(duì)象的所有權(quán)--通過(guò)向它發(fā)送一個(gè)retain
消息--來(lái)確保這個(gè)新值不會(huì)被丟棄定鸟。同樣也必須放棄舊值的所有權(quán)通過(guò)向舊值發(fā)送一個(gè)release
消息(在Objective-C中向nil
發(fā)送消息是允許的,如果_count
還沒(méi)有被賦值的話(huà)那么實(shí)現(xiàn)仍會(huì)起作用)著瓶。必須在[newCount retain]
之后發(fā)送release
消息以免新值和舊值是同一個(gè)對(duì)象--你絕對(duì)不想意外的讓這個(gè)對(duì)象被銷(xiāo)毀联予。
- (void)setCount:(NSNumber *)newCount {
[newCount retain];
[_count release];
// Make the new assignment.
_count = newCount;
}
使用存取方法設(shè)置屬性值
假設(shè)你想實(shí)現(xiàn)一個(gè)方法來(lái)重置這個(gè)計(jì)數(shù)器。你有幾個(gè)選擇材原。第一個(gè)實(shí)現(xiàn)是使用alloc
創(chuàng)建這個(gè)NSNumber
實(shí)例沸久,因此需要發(fā)送release
消息來(lái)達(dá)到平衡。
- (void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[self setCount:zero];
[zero release];
}
其次是使用一個(gè)便利構(gòu)造器來(lái)創(chuàng)建一個(gè)新的NSNumber
對(duì)象余蟹。因此沒(méi)必要發(fā)送retain
和release
消息
- (void)reset {
NSNumber *zero = [NSNumber numberWithInteger:0];
[self setCount:zero];
}
注意以上兩個(gè)方法都使用了set方法卷胯。
下面的方法對(duì)于簡(jiǎn)單的例子大部分情況下也會(huì)正確工作,但是像繞開(kāi)存取方法一樣誘人威酒,這樣做將會(huì)很可能在某些階段導(dǎo)致錯(cuò)誤(例如窑睁,當(dāng)你忘記了retain
或者release
,或者對(duì)這個(gè)實(shí)例變量的內(nèi)存管理語(yǔ)義發(fā)生變化時(shí))葵孤。
- (void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[_count release];
_count = zero;
}
注意卵慰,如果你使用KVO,使用這種方式改變這個(gè)變量是不被KVO允許的佛呻。
不要在初始化和dealloc方法中使用存取方法
只有兩個(gè)地方不應(yīng)該使用存取方法來(lái)設(shè)置一個(gè)實(shí)例變量:初始化方法和dealloc
方法裳朋。以一個(gè)代表0的值對(duì)象初始化一個(gè)計(jì)數(shù)器對(duì)象,可以像下面這樣實(shí)現(xiàn)一個(gè)init
方法:
- init {
self = [super init];
if (self) {
_count = [[NSNumber alloc] initWithInteger:0];
}
return self;
}
為了初始化一個(gè)非0的計(jì)數(shù)器對(duì)象吓著,可以像下面這樣實(shí)現(xiàn)一個(gè)initWithCount:
方法:
- initWithCount:(NSNumber *)startingCount {
self = [super init];
if (self) {
_count = [startingCount copy];
}
return self;
}
因?yàn)橛?jì)數(shù)器類(lèi)有一個(gè)對(duì)象類(lèi)型的實(shí)例變量鲤嫡,所以必須實(shí)現(xiàn)一個(gè)dealloc
方法。計(jì)數(shù)器類(lèi)應(yīng)該放棄任何實(shí)例變量的所有權(quán)通過(guò)發(fā)送release
消息绑莺,并在最后調(diào)用父類(lèi)的實(shí)現(xiàn):
- (void)dealloc {
[_count release];
[super dealloc];
}
3.2.使用弱引用避免循環(huán)引用
持有一個(gè)對(duì)象會(huì)產(chǎn)生對(duì)這個(gè)對(duì)象的強(qiáng)引用暖眼。一個(gè)對(duì)象只有當(dāng)它所有的強(qiáng)引用被釋放時(shí)才會(huì)被銷(xiāo)毀。如果兩個(gè)對(duì)象有了循環(huán)引用--也就是說(shuō)彼此都有一個(gè)強(qiáng)引用(可以是直接的纺裁,也可以是通過(guò)其它彼此從頭到尾都擁有一個(gè)強(qiáng)引用的對(duì)象)诫肠,那么一個(gè)叫做“引用環(huán)”的問(wèn)題就會(huì)產(chǎn)生。
圖1中的對(duì)象關(guān)系展示了一個(gè)潛在的引用環(huán)欺缘。Document 對(duì)象對(duì)于文檔中的每個(gè)page有一個(gè)Page對(duì)象栋豫。每個(gè)Page對(duì)象都有一個(gè)屬性記錄它屬于哪個(gè)文檔。如果Document 對(duì)象對(duì)Page對(duì)象持有一個(gè)強(qiáng)引用谚殊,Page 對(duì)象對(duì)Document對(duì)象持有一個(gè)強(qiáng)引用丧鸯,那么這兩個(gè)對(duì)象永遠(yuǎn)也不會(huì)被銷(xiāo)毀。Document的引用計(jì)數(shù)永遠(yuǎn)也不會(huì)為0直到Page對(duì)象被釋放嫩絮,同樣的Page對(duì)象永遠(yuǎn)也不會(huì)被釋放直到Document對(duì)象被釋放丛肢。
解決引用環(huán)問(wèn)題的方法是使用弱引用围肥。一個(gè)弱引用是一個(gè)非持有的關(guān)系--源對(duì)象并不持有它引用的對(duì)象。
但是為了對(duì)象圖表的完整性蜂怎,在某些地方必須存在強(qiáng)引用(如果只有弱引用穆刻,那么pages和paragraphs就不會(huì)有任何所有者,也就會(huì)被銷(xiāo)毀)杠步。Cocoa建立了一套規(guī)則蛹批,“父類(lèi)”對(duì)象應(yīng)該對(duì)它的“子類(lèi)”持有強(qiáng)引用,“子類(lèi)”對(duì)象應(yīng)該對(duì)它的“父類(lèi)”持有弱引用篮愉。
因此,在圖1中差导,Document 對(duì)象對(duì)它的Page 對(duì)象持有強(qiáng)引用试躏,Page 對(duì)象對(duì)它的Document 對(duì)象持有弱引用。
Cocoa中的弱引用例子包括:table的數(shù)據(jù)源设褐,IBOutlet連接的視圖項(xiàng)颠蕴,通知的觀察者,各種各樣的targets和代理等助析。
當(dāng)向一個(gè)你持有弱引用的對(duì)象發(fā)送消息時(shí)要格外小心犀被。如果在一個(gè)對(duì)象被銷(xiāo)毀后向它發(fā)送了一個(gè)消息,程序會(huì)crash外冀。必須明確的知道這個(gè)對(duì)象何時(shí)是有效的寡键。在大多數(shù)情況下,弱引用對(duì)象知道其它對(duì)象對(duì)它的弱引用雪隧,同循環(huán)引用一樣西轩,當(dāng)它銷(xiāo)毀時(shí)負(fù)責(zé)通知其它對(duì)象。例如脑沿,當(dāng)你用通知中心注冊(cè)了一個(gè)對(duì)象藕畔,通知中心會(huì)存儲(chǔ)一個(gè)對(duì)這個(gè)對(duì)象的弱引用,當(dāng)合適的通知發(fā)出后庄拇,會(huì)向這個(gè)對(duì)象發(fā)送消息注服。當(dāng)這個(gè)對(duì)象被銷(xiāo)毀后,你需要在通知中心對(duì)它進(jìn)行移除注冊(cè)來(lái)避免通知中心未來(lái)向這個(gè)已經(jīng)被銷(xiāo)毀的對(duì)象發(fā)送消息措近。同樣的溶弟,當(dāng)一個(gè)代理對(duì)象被銷(xiāo)毀時(shí),你需要通過(guò)發(fā)送
setDelegate:
消息傳入nil
參數(shù)來(lái)移除這個(gè)代理連接瞭郑。這些消息通常在dealloc
方法發(fā)送可很。
3.3.避免引起正在使用的對(duì)象的銷(xiāo)毀
Cocoa的所有權(quán)策略指明接收對(duì)象在方法調(diào)用的范圍內(nèi)應(yīng)該持續(xù)有效。同樣也可以從當(dāng)前的范圍返回一個(gè)接收對(duì)象而無(wú)需擔(dān)心這個(gè)對(duì)象被釋放凰浮。對(duì)你的應(yīng)用來(lái)說(shuō)一個(gè)對(duì)象的getter方法無(wú)論是返回一個(gè)緩存的實(shí)例變量還是一個(gè)計(jì)算的值是無(wú)所謂的我抠。重要的是這個(gè)對(duì)象在你使用它時(shí)是有效的苇本。
針對(duì)這個(gè)策略偶爾還是有些例外的,主要在以下兩類(lèi)菜拓。
- 1.當(dāng)一個(gè)對(duì)象從基礎(chǔ)的集合類(lèi)中被移除時(shí)瓣窄。
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.
當(dāng)一個(gè)對(duì)象從基礎(chǔ)的集合類(lèi)中被移除時(shí),它會(huì)收到一條release
而不是autorelease
消息纳鼎。如果這個(gè)集合是這個(gè)被移除對(duì)象的唯一持有者俺夕,這個(gè)被移除對(duì)象(例子中的heisenObject
)會(huì)立刻被銷(xiāo)毀。
- 2.當(dāng)一個(gè)“父類(lèi)對(duì)象”被銷(xiāo)毀時(shí)贱鄙。
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.
在某些情況下你從另外一個(gè)對(duì)象獲取到一個(gè)對(duì)象劝贸,然后直接或間接地釋放了這個(gè)父類(lèi)對(duì)象。如果釋放父類(lèi)對(duì)象導(dǎo)致父類(lèi)對(duì)象被銷(xiāo)毀逗宁,父類(lèi)對(duì)象恰巧是這個(gè)子類(lèi)的唯一所有者映九,那么子類(lèi)會(huì)同時(shí)被銷(xiāo)毀(假設(shè)子類(lèi)在父類(lèi)的dealloc
方法中收到了release
而不是autorelease
消息)。
為了避免這些情況瞎颗,一旦接收到heisenObject
就retain它件甥,使用完畢后release它,例如:
heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];
3.4.不要使用dealloc管理稀缺資源
通常的不要在dealloc
方法中處理稀缺資源哼拔,例如文件描述符引有,網(wǎng)絡(luò)連接,緩沖區(qū)或者緩存倦逐。尤其不要設(shè)計(jì)這樣的類(lèi):當(dāng)你認(rèn)為dealloc
方法將會(huì)被調(diào)起時(shí)它就會(huì)被調(diào)起譬正。dealloc
方法的調(diào)起也許會(huì)延遲或者干脆就不調(diào)起,這可能是因?yàn)橐粋€(gè)bug或者應(yīng)用tear-down檬姥。
相反导帝,如果你有一個(gè)類(lèi)它的實(shí)例變量管理著稀缺資源,你應(yīng)該這樣設(shè)計(jì)你的應(yīng)用:你知道何時(shí)不再需要這些資源并且同時(shí)通知實(shí)例來(lái)清理這些資源穿铆。然后釋放這個(gè)實(shí)例變量您单,緊接著dealloc
會(huì)被調(diào)起,但是你不會(huì)遭到額外的問(wèn)題即使dealloc
未被調(diào)起荞雏。
如果你嘗試在dealloc
方法中處理資源管理虐秦,那么可能導(dǎo)致以下問(wèn)題:
- 1.順序取決于對(duì)象圖表tear-down機(jī)制
對(duì)象圖表tear-down機(jī)制本身是無(wú)序的。盡管你也許期望--或者得到了一個(gè)特別的順序凤优,實(shí)際這種順序是很脆弱的悦陋。如果一個(gè)對(duì)象被不可預(yù)期的自動(dòng)釋放而不是立刻釋放,tear-down順序也許就會(huì)改變筑辨,這也許會(huì)導(dǎo)致不可預(yù)期的結(jié)果俺驶。 - 2.稀缺資源沒(méi)有重復(fù)利用。
內(nèi)存泄漏是bug需要修復(fù)棍辕,但是它們通常不會(huì)立刻導(dǎo)致致命問(wèn)題暮现。但是如果稀有資源沒(méi)有在你期望釋放時(shí)釋放还绘,那么你也許會(huì)陷入嚴(yán)重問(wèn)題。例如栖袋,如果你的程序用光了文件描述符拍顷,用戶(hù)也許無(wú)法保存數(shù)據(jù)。 - 3.在錯(cuò)誤的線(xiàn)程執(zhí)行清理邏輯的操作塘幅。
如果一個(gè)對(duì)象在一個(gè)不可預(yù)料的時(shí)間被釋放昔案,它將會(huì)在它恰巧處在的任何線(xiàn)程的自動(dòng)釋放池代碼塊內(nèi)被釋放。這對(duì)于應(yīng)該只在一個(gè)線(xiàn)程訪問(wèn)的資源來(lái)說(shuō)很可能導(dǎo)致致命問(wèn)題电媳。
3.5.集合持有其包含的對(duì)象
當(dāng)你向一個(gè)集合添加了一個(gè)對(duì)象踏揣,這個(gè)集合就會(huì)持有這個(gè)對(duì)象的所有權(quán)。當(dāng)對(duì)象從集合移除或者集合本身被釋放時(shí)匾乓,集合會(huì)放棄對(duì)象的所有權(quán)捞稿。例如,如果你想創(chuàng)建一個(gè)數(shù)值組成的數(shù)組钝尸,可以有以下兩種方式:
NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
[array addObject:convenienceNumber];
}
這個(gè)例子中,你沒(méi)有調(diào)用alloc
搂根,所以沒(méi)必要調(diào)用release
珍促。沒(méi)必要retainconvenienceNumber
,因?yàn)閿?shù)組會(huì)幫你做剩愧。
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];
}
這個(gè)例子中猪叙,你需要在for
循環(huán)內(nèi)向allocedNumber
發(fā)送release
消息來(lái)平衡alloc
。因?yàn)閿?shù)組在調(diào)用addObject:
添加值時(shí)持有了它仁卷,所以在這個(gè)值只要在數(shù)組內(nèi)就不會(huì)被銷(xiāo)毀穴翩。
3.6.所有權(quán)策略是通過(guò)引用計(jì)數(shù)來(lái)實(shí)現(xiàn)的
所有權(quán)策略是通過(guò)引用計(jì)數(shù)來(lái)實(shí)現(xiàn)的--通常叫做“retain count”。每個(gè)對(duì)象都有一個(gè)retain count锦积。
- 當(dāng)你創(chuàng)建了一個(gè)對(duì)象芒帕,它的retain count為1。
- 當(dāng)你向一個(gè)對(duì)象發(fā)送一條
retain
消息丰介,它的retain count加1 - 當(dāng)你向一個(gè)對(duì)象發(fā)送一條
release
消息背蟆,它的retain count減1
當(dāng)你向一個(gè)對(duì)象發(fā)送一條autorelease
消息,它的retain count會(huì)在當(dāng)前的autorelease pool block末尾減1 - 如果一個(gè)對(duì)象的retain count減到0哮幢,它就會(huì)被銷(xiāo)毀
重要:沒(méi)有理由明確的請(qǐng)求一個(gè)對(duì)象的retain count带膀。結(jié)果經(jīng)常是錯(cuò)誤的,因?yàn)槟阋苍S不清楚什么框架的對(duì)象持有了一個(gè)你感興趣的對(duì)象橙垢。在調(diào)試內(nèi)存管理問(wèn)題時(shí)垛叨,你應(yīng)該只關(guān)心確保你的代碼堅(jiān)持了所有權(quán)規(guī)則。
4.使用自動(dòng)釋放池代碼塊
自動(dòng)釋放池代碼塊提供了一種機(jī)制:你可以放棄一個(gè)對(duì)象的所有權(quán)柜某,但是可以避免這個(gè)對(duì)象被立即銷(xiāo)毀(例如當(dāng)你從一個(gè)方法返回一個(gè)對(duì)象時(shí))嗽元。通常你不需要?jiǎng)?chuàng)建你自己的自動(dòng)釋放池代碼塊敛纲,但是有些情況下你不得不創(chuàng)建或者創(chuàng)建是有利的。
4.1.關(guān)于自動(dòng)釋放池代碼塊
一個(gè)自動(dòng)釋放池代碼塊以@autoreleasepool
標(biāo)記还棱,像下面這樣:
@autoreleasepool {
// Code that creates autoreleased objects.
}
在自動(dòng)釋放池代碼塊末尾载慈,在塊內(nèi)接收了autorelease
消息的對(duì)象會(huì)接收一條release
消息--對(duì)象每在塊內(nèi)接收一次autorelease
消息,就會(huì)在塊末尾收到一條release
消息珍手。
像其它代碼塊一樣办铡,自動(dòng)釋放池代碼塊也可以嵌套:
@autoreleasepool {
// . . .
@autoreleasepool {
// . . .
}
. . .
}
對(duì)于一條指定的autorelease
消息,對(duì)應(yīng)的release
消息會(huì)在autorelease
消息被發(fā)送的自動(dòng)釋放池代碼塊末尾發(fā)送琳要。
Cocoa總是期望代碼在自動(dòng)釋放池代碼塊內(nèi)執(zhí)行寡具,否則自動(dòng)釋放的對(duì)象得不到釋放你的應(yīng)用就會(huì)泄漏內(nèi)存(如果在自動(dòng)釋放池代碼塊外發(fā)送autorelease
消息,Cocoa會(huì)報(bào)錯(cuò))稚补。AppKit和UIKit框架在自動(dòng)釋放池代碼塊內(nèi)處理每個(gè)時(shí)間循環(huán)迭代(例如鼠標(biāo)下移或者點(diǎn)擊)童叠。因此你通常不需要自己創(chuàng)建一個(gè)自動(dòng)釋放池代碼塊,或者甚至看不到這樣的代碼课幕。但是厦坛,有三種情況你可能需要使用你自己的自動(dòng)釋放池代碼塊:
- 如果你在編寫(xiě)一個(gè)不是基于UI框架的項(xiàng)目,例如命令行工具
- 如果你編寫(xiě)了一個(gè)產(chǎn)生許多臨時(shí)對(duì)象的循環(huán)
你也許可以在下一個(gè)迭代之前在這個(gè)循環(huán)內(nèi)部使用一個(gè)自動(dòng)釋放池代碼塊來(lái)去除那些臨時(shí)對(duì)象乍惊。在循環(huán)內(nèi)使用一個(gè)自動(dòng)釋放池代碼塊可以幫助降低程序的內(nèi)存峰值杜秸。 - 如果你大量創(chuàng)建了子線(xiàn)程。
你必須在線(xiàn)程開(kāi)始執(zhí)行前創(chuàng)建你自己的自動(dòng)釋放池代碼塊润绎;否則你的應(yīng)用將會(huì)泄漏內(nèi)存撬碟。
4.2.使用局部的自動(dòng)釋放池代碼塊降低內(nèi)存峰值
許多程序會(huì)創(chuàng)建自動(dòng)釋放的臨時(shí)對(duì)象。這些對(duì)象會(huì)添加到程序的內(nèi)存中直到block的結(jié)尾莉撇。在許多情況下呢蛤,允許臨時(shí)對(duì)象在當(dāng)前時(shí)間循環(huán)迭代結(jié)束前累計(jì)不會(huì)導(dǎo)致過(guò)多的開(kāi)銷(xiāo);但是在一些情況下棍郎,你也許會(huì)創(chuàng)建大量的臨時(shí)對(duì)象持續(xù)的添加到內(nèi)存中其障,你想更快速的處理掉它們。這時(shí)涂佃,你可以創(chuàng)建你自己的自動(dòng)釋放池代碼塊静秆。在塊的末尾,這些臨時(shí)對(duì)象會(huì)被銷(xiāo)毀巡李,從而降低程序的內(nèi)存抚笔。
下面的例子展示了如何在for
循環(huán)內(nèi)使用局部的自動(dòng)釋放池代碼塊。
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
for
循環(huán)每次處理一個(gè)文件侨拦。任何在塊內(nèi)接收autorelease
消息的對(duì)象(例如fileContents
)都會(huì)在塊的末尾被釋放殊橙。
在一個(gè)自動(dòng)釋放池代碼塊之后,你應(yīng)該將任何在塊內(nèi)自動(dòng)釋放的對(duì)象當(dāng)做被處理掉了。不要向這個(gè)對(duì)象發(fā)送消息或者將這個(gè)對(duì)象返回給方法的調(diào)用者膨蛮。如果你必須要在塊外使用一個(gè)臨時(shí)對(duì)象叠纹,那么應(yīng)該在塊內(nèi)向這個(gè)對(duì)象發(fā)送retain
消息,塊外發(fā)送autorelease
消息敞葛,像下面這樣:
– (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. */
}
在塊內(nèi)向match
發(fā)送retain
消息并且在塊外發(fā)送autorelease
消息擴(kuò)展了match
的聲明周期誉察,允許它在循環(huán)外部接收消息并返回給findMatchingObject:
的調(diào)用者。
4.3.自動(dòng)釋放池代碼塊和線(xiàn)程
Cocoa應(yīng)用的每個(gè)線(xiàn)程都維持著它自己的自動(dòng)釋放池代碼塊棧惹谐。如果你編寫(xiě)的是純Foundation的程序或者detach了一個(gè)線(xiàn)程持偏,你需要?jiǎng)?chuàng)建自己的自動(dòng)釋放池代碼塊。
如果你的應(yīng)用或者線(xiàn)程是長(zhǎng)期存在的氨肌,并且可能產(chǎn)生大量的自動(dòng)釋放對(duì)象鸿秆,你應(yīng)該使用自動(dòng)釋放池代碼塊。否則怎囚,自動(dòng)釋放的對(duì)象會(huì)累積卿叽,你的內(nèi)存會(huì)增長(zhǎng)。如果你detached的線(xiàn)程沒(méi)有做Cocoa調(diào)用恳守,那么沒(méi)必要使用自動(dòng)釋放池代碼塊考婴。
注意:如果你使用POSIX線(xiàn)程API而不是NSThread
開(kāi)辟了子線(xiàn)程,你不能使用Cocoa除非Cocoa在多線(xiàn)程模式催烘。Cocoa 只在分離出它的第一個(gè)NSThread
對(duì)象時(shí)進(jìn)入多線(xiàn)程模式沥阱。為了在POSIX的子線(xiàn)程使用Cocoa,你的程序必須首先分離出至少一個(gè)可以立刻退出的NSThread
對(duì)象颗圣≡樱可以使用NSThread
類(lèi)的isMultiThreaded
方法測(cè)試是否在多線(xiàn)程模式屁使。
5.參考文獻(xiàn)
提升代碼質(zhì)量最神圣的三部曲:模塊設(shè)計(jì)(謀定而后動(dòng)) -->無(wú)錯(cuò)編碼(知止而有得) -->開(kāi)發(fā)自測(cè)(防患于未然)