高級(jí)內(nèi)存管理編程指南

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ì)象(例如allocnewObject或者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ò)誤的信息誓沸。
例如NSDatainitWithContentsOfURL:options:error:方法梅桩,或者NSStringinitWithContentsOfFile: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í)例變量充斥著retainrelease辆琅,那么實(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í)例變量咐刨,因此不需要retainrelease

- (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ā)送retainrelease消息

- (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)引用圖示

解決引用環(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è)(防患于未然)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末在岂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蛮寂,更是在濱河造成了極大的恐慌蔽午,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酬蹋,死亡現(xiàn)場(chǎng)離奇詭異及老,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)范抓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)骄恶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人匕垫,你說(shuō)我怎么就攤上這事僧鲁。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵寞秃,是天一觀的道長(zhǎng)斟叼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)春寿,這世上最難降的妖魔是什么朗涩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮绑改,結(jié)果婚禮上谢床,老公的妹妹穿的比我還像新娘。我一直安慰自己绢淀,他們只是感情好萤悴,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著皆的,像睡著了一般覆履。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上费薄,一...
    開(kāi)封第一講書(shū)人閱讀 52,696評(píng)論 1 312
  • 那天硝全,我揣著相機(jī)與錄音,去河邊找鬼楞抡。 笑死伟众,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的召廷。 我是一名探鬼主播凳厢,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼竞慢!你這毒婦竟也來(lái)了先紫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤筹煮,失蹤者是張志新(化名)和其女友劉穎遮精,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體败潦,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡本冲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劫扒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檬洞。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沟饥,靈堂內(nèi)的尸體忽然破棺而出添怔,到底是詐尸還是另有隱情环戈,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布澎灸,位于F島的核電站院塞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏性昭。R本人自食惡果不足惜拦止,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糜颠。 院中可真熱鬧汹族,春花似錦、人聲如沸其兴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)元旬。三九已至榴徐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匀归,已是汗流浹背坑资。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留穆端,地道東北人袱贮。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像体啰,于是被迫代替她去往敵國(guó)和親攒巍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容