Objective-C 采用的是引用計(jì)數(shù)式的內(nèi)存管理方式:
自己生成的對(duì)象自己持有。
非自己生成的對(duì)象自己也能持有。
自己持有的對(duì)象不再需要時(shí)釋放。
非自己持有的對(duì)象自己無(wú)法釋放。
使用以下名稱開頭的方法名意味著自己生成的對(duì)象只有自己持有:
alloc
new
copy
mutableCopy
/*
* 自己生成并持有該對(duì)象
*/
id obj0 = [[NSObeject alloc] init];
id obj1 = [NSObeject new];
- 非自己生成的對(duì)象,自己也能持有
/*
* 持有非自己生成的對(duì)象
*/
id obj = [NSArray array]; // 非自己生成的對(duì)象,且該對(duì)象存在,但自己不持有
[obj retain]; // 自己持有對(duì)象
備注:通過 retain
方法來讓指針變量持有這個(gè)新生成的對(duì)象。
- 不再需要自己持有的對(duì)象時(shí)釋放
/*
* 不在需要自己持有的對(duì)象的時(shí)候,釋放
*/
id obj = [[NSObeject alloc] init]; // 此時(shí)持有對(duì)象
[obj release]; // 釋放對(duì)象
/*
* 指向?qū)ο蟮闹羔樔跃捅槐A粼趏bj這個(gè)變量中
* 但對(duì)象已經(jīng)釋放,不可訪問
*/
自己持有的對(duì)象,一旦不再需要,持有者有義務(wù)釋放該對(duì)象。釋放使用 release
方法。
當(dāng)調(diào)用對(duì)象的 release
方法只是將對(duì)象的引用計(jì)數(shù)器 -1
,當(dāng)對(duì)象的引用計(jì)數(shù)器為 0
的時(shí)候會(huì)調(diào)用了對(duì)象的 dealloc
方法才能進(jìn)行釋放對(duì)象的內(nèi)存。
- 非自己生成的對(duì)象持有對(duì)象的釋放
//非自己生成的對(duì)象,暫時(shí)沒有持有
id obj = [NSMutableArray array];
//通過retain持有對(duì)象
[obj retain];
//釋放對(duì)象
[obj release];
兩種不允許的情況:
- 釋放自己不持有的對(duì)象
/*
* 非自己持有的對(duì)象無(wú)法釋放
*/
id obj = [NSArray array]; // 非自己生成的對(duì)象,且該對(duì)象存在,但自己不持有
[obj release]; // ~~~此時(shí)將運(yùn)行時(shí)crash 或編譯器報(bào)error~~~ 非 ARC 下,調(diào)用該方法會(huì)導(dǎo)致編譯器報(bào) issues。此操作的行為是未定義的,可能會(huì)導(dǎo)致運(yùn)行時(shí) crash 或者其它未知行為
- 釋放一個(gè)已經(jīng)廢棄了的對(duì)象
id obj = [[NSObject alloc] init];//持有新生成的對(duì)象
[obj doSomething];//使用該對(duì)象
[obj release];//釋放該對(duì)象,不再持有了
[obj release];//釋放已經(jīng)廢棄了的對(duì)象,崩潰
autorelease
當(dāng)對(duì)象超出其作用域時(shí),對(duì)象實(shí)例的 release
方法就會(huì)被調(diào)用,autorelease
的具體使用方法如下:
- 生成并持有
NSAutoreleasePool
對(duì)象。 - 調(diào)用已分配對(duì)象的
autorelease
方法。 - 廢棄
NSAutoreleasePool
對(duì)象。
- (id) getAObjNotRetain {
id obj = [[NSObject alloc] init]; // 自己持有對(duì)象
[obj autorelease]; // 取得的對(duì)象存在,但自己不持有該對(duì)象
return obj;
}
這個(gè)特性是使用 autorelease
來實(shí)現(xiàn)的,autorelease
使得對(duì)象在超出生命周期后能正確的被釋放(通過調(diào)用 release
方法)。在調(diào)用 release
后,對(duì)象會(huì)被立即釋放,而調(diào)用 autorelease
后,對(duì)象不會(huì)被立即釋放,而是注冊(cè)到 autoreleasepool
中,當(dāng) autoreleasepool
銷毀時(shí),會(huì)對(duì) autoreleasepool
里面的所有對(duì)象做一次 release
操作。
在 ARC
環(huán)境下,id
類型和對(duì)象類型和 C
語(yǔ)言其他類型不同,類型前必須加上所有權(quán)的修飾符。
所有權(quán)修飾符總共有4種:
- __strong
- __weak
- __autoreleasing
- __unsafe_unretained
__strong
__strong
表示強(qiáng)引用,對(duì)應(yīng)定義 property
時(shí)用到的 strong
。當(dāng)對(duì)象沒有任何一個(gè)強(qiáng)引用指向它時(shí),它才會(huì)被釋放。如果在聲明引用時(shí)不加修飾符,那么引用將默認(rèn)是強(qiáng)引用。當(dāng)需要釋放強(qiáng)引用指向的對(duì)象時(shí),需要保證所有指向?qū)ο髲?qiáng)引用置為 nil
。__strong
修飾符是 id
類型和對(duì)象類型默認(rèn)的所有權(quán)修飾符。
__weak
__weak
表示弱引用,對(duì)應(yīng)定義 property
時(shí)用到的 weak
。弱引用不會(huì)影響對(duì)象的釋放,而當(dāng)對(duì)象被釋放時(shí),所有指向它的弱引用都會(huì)自定被置為 nil
,這樣可以防止野指針。__weak
最常見的一個(gè)作用就是用來避免強(qiáng)引用循環(huán)。
__weak
的幾個(gè)使用場(chǎng)景:
- 在
delegate
關(guān)系中防止強(qiáng)引用循環(huán)。在ARC
特性下,通常我們應(yīng)該設(shè)置delegate
屬性為weak
的。但是這里有一個(gè)疑問,我們常用到的UITableView
的delegate
屬性是這樣定義的:@property (nonatomic, assign) id<UITableViewDelegate> delegate;
,為什么用的修飾符是assign
而不是weak
?其實(shí)這個(gè)assign
在ARC
中意義等同于__unsafe_unretained
(后面會(huì)講到),它是為了在ARC
特性下兼容iOS4
及更低版本來實(shí)現(xiàn)弱引用機(jī)制。一般情況下,你應(yīng)該盡量使用weak
。 - 在
Block
中防止強(qiáng)引用循環(huán)。 - 用來修飾指向由
Interface Builder
創(chuàng)建的控件。比如:@property (nonatomic, weak) IBOutlet UIButton *testButton;
。
另外,__weak
修飾符的變量,會(huì)被注冊(cè)到 autoreleasePool
中。
{
id __weak obj1 = obj;
NSLog(@"obj2-%@",obj1);
}
編譯器轉(zhuǎn)換上述代碼如下:
id obj1;
objc_initweak(&obj1,obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destroyWeak(&obj1);
objc_loadWeakRetained
函數(shù)獲取附有 __weak
修飾符變量所引用的對(duì)象并 retain
, objc_autorelease
函數(shù)將對(duì)象放入 autoreleasePool
中,據(jù)此當(dāng)我們?cè)L問 weak
修飾指針指向的對(duì)象時(shí),實(shí)際上是訪問注冊(cè)到自動(dòng)釋放池的對(duì)象。因此,如果大量使用 weak
的話,在我們?nèi)ピL問 weak
修飾的對(duì)象時(shí),會(huì)有大量對(duì)象注冊(cè)到自動(dòng)釋放池,這會(huì)影響程序的性能。
解決方案:
要訪問 weak
修飾的變量時(shí),先將其賦給一個(gè) strong
變量,然后進(jìn)行訪問。
為什么訪問 weak
修飾的對(duì)象就會(huì)訪問注冊(cè)到自動(dòng)釋放池的對(duì)象呢?
因?yàn)?weak
不會(huì)引起對(duì)象的引用計(jì)數(shù)器變化,因此,該對(duì)象在運(yùn)行過程中很有可能會(huì)被釋放。所以,需要將對(duì)象注冊(cè)到自動(dòng)釋放池中并在 autoreleasePool
銷毀時(shí)釋放對(duì)象占用的內(nèi)存。
__autoreleasing
在 ARC
模式下,我們不能顯示的使用 autorelease
方法了,但是 autorelease
的機(jī)制還是有效的,通過將對(duì)象賦給 __autoreleasing
修飾的變量就能達(dá)到在 MRC
模式下調(diào)用對(duì)象的 autorelease
方法同樣的效果。
__autoreleasing
修飾的對(duì)象會(huì)被注冊(cè)到 Autorelease Pool
中,并在 Autorelease Pool
銷毀時(shí)被釋放。
注意:定義 property
時(shí)不能使用這個(gè)修飾符,因?yàn)槿魏我粋€(gè)對(duì)象的 property
都不應(yīng)該是 autorelease
類型的。
__unsafe_unretained
ARC
是在 iOS5
引入的,而 __unsafe_unretained
這個(gè)修飾符主要是為了在 ARC
剛發(fā)布時(shí)兼容 iOS4
以及版本更低的系統(tǒng),因?yàn)檫@些版本沒有弱引用機(jī)制。這個(gè)修飾符在定義 property
時(shí)對(duì)應(yīng)的是 unsafe_unretained
。__unsafe_unretained
修飾的指針純粹只是指向?qū)ο?,沒有任何額外的操作,不會(huì)去持有對(duì)象使得對(duì)象的 retainCount +1
。而在指向的對(duì)象被釋放時(shí)依然原原本本地指向原來的對(duì)象地址,不會(huì)被自動(dòng)置為 nil
,所以成為了野指針,非常不安全。
__unsafe_unretained
的應(yīng)用場(chǎng)景:
- 在 ARC 環(huán)境下但是要兼容 iOS4.x 的版本,用
__unsafe_unretained
替代__weak
解決強(qiáng)引用循環(huán)的問題。
最后
總結(jié), autorelease
的機(jī)制卻依然在很多地方默默起著作用,我們來看看這些場(chǎng)景:
- 方法返回值。
- 訪問 __weak 修飾的變量。
- id 的指針或?qū)ο蟮闹羔?id *)。
方法返回值
首先,我們看這個(gè)方法:
- (NSMutableArray *)array {
NSMutableArray *array = [NSMutableArray array];
return array;
}
轉(zhuǎn)化為
NSMutableArray *array = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(array);
objc_release(array);
這里 array
的所有權(quán)修飾符是默認(rèn)的 __strong
。由于 return
使得 array
超出其作用域,它強(qiáng)引用持有的對(duì)象本該被釋放,但是由于該對(duì)象作為函數(shù)返回值,所以一般情況下編譯器會(huì)自動(dòng)將其注冊(cè)到 AutoreleasePool
中(注意這里是一般情況下,在一些特定情況下,ARC
機(jī)制提出了巧妙的運(yùn)行時(shí)優(yōu)化方案來跳過 autorelease
機(jī)制。)。
ARC 模式下方法返回值跳過 autorelease 機(jī)制的優(yōu)化方案
為什么方法返回值的時(shí)候需要用到 autorelease
機(jī)制呢?
當(dāng)對(duì)象被作為參數(shù)返回 return
之后,如果調(diào)用者需要使用就需要強(qiáng)引用它,那么它 retainCount + 1
,用完之后再清理,使它 retainCount - 1
。
如果在方法中創(chuàng)建了對(duì)象并作為返回值時(shí),根據(jù) ARC
內(nèi)存管理的原則,誰(shuí)創(chuàng)建誰(shuí)釋放。既然作為返回值,就必須保證返回時(shí)對(duì)象沒被釋放以便方法外的調(diào)用者能拿到有效的對(duì)象,否則你返回的是 nil,有何意義呢。所以就需要找一個(gè)合理的機(jī)制既能延長(zhǎng)這個(gè)對(duì)象的生命周期,又能保證對(duì)其釋放。這個(gè)機(jī)制就是 autorelease 機(jī)制
。
ARC
模式下在方法 return
的時(shí)候,會(huì)調(diào)用 objc_autoreleaseReturnValue()
方法替代 autorelease
。在調(diào)用者強(qiáng)引用方法返回對(duì)象的時(shí)候,會(huì)調(diào)用 objc_retainAutoreleasedReturnValue()
方法,該方法會(huì)去檢查該方法或者調(diào)用方的執(zhí)行命令列表,是否會(huì)被傳給 objc_retainAutoreleasedReturnValue()
方法。如果里面有 objc_retainAutoreleasedReturnValue()
方法,那么該對(duì)象就直接返回給方法或者函數(shù)的調(diào)用方。達(dá)到了即使對(duì)象不注冊(cè)到 autoreleasepool
中,也可以返回拿到相應(yīng)的對(duì)象。如果沒傳,那么它就會(huì)走 autorelease
的過程注冊(cè)到 autoreleasepool
中。
訪問 __weak 修飾的變量
在訪問 __weak
修飾的變量時(shí),實(shí)際上必定會(huì)訪問注冊(cè)到 AutoreleasePool
的對(duì)象。如下來年兩段代碼是相同的效果:
id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
// 等同于:
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);
為什么會(huì)這樣呢?因?yàn)?__weak
修飾符只持有對(duì)象的弱引用,而在訪問對(duì)象的過程中,該對(duì)象有可能被廢棄,如果把被訪問的對(duì)象注冊(cè)到 AutoreleasePool
中,就能保證 AutoreleasePool
被銷毀前對(duì)象是存在的。
id 的指針或?qū)ο蟮闹羔?id *)
另一個(gè)隱式地使用 __autoreleasing
的例子就是使用 id 的指針或?qū)ο蟮闹羔?id *) 的時(shí)候。
看一個(gè)最常見的例子:
NSError *__autoreleasing error;
if (![data writeToFile:filename options:NSDataWritingAtomic error:&error]) {
NSLog(@"Error: %@", error);
}
// 即使上面你沒有寫 __autoreleasing 來修飾 error,編譯器也會(huì)幫你做下面的事情:
NSError *error;
NSError *__autoreleasing tempError = error; // 編譯器添加
if (![data writeToFile:filename options:NSDataWritingAtomic error:&tempError]) {
error = tempError; // 編譯器添加
NSLog(@"Error: %@", error);
}
error
對(duì)象在你調(diào)用的方法中被創(chuàng)建,然后被放到 AutoreleasePool
中,等到使用結(jié)束后隨著 AutoreleasePool
的銷毀而釋放,所以函數(shù)外 error
對(duì)象的使用者不需要關(guān)心它的釋放。
在 ARC
中,所有這種指針的指針類型(id *)
的函數(shù)參數(shù)如果不加修飾符,編譯器會(huì)默認(rèn)將他們認(rèn)定為 __autoreleasing
類型。
有一點(diǎn)特別需要注意的是,某些類的方法會(huì)隱式地使用自己的 AutoreleasePool
,在這種時(shí)候使用 __autoreleasing
類型要特別小心。比如 NSDictionary
的 enumerateKeysAndObjectsUsingBlock
方法:
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error {
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// do stuff
if (there is some error && error != nil) {
*error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
}
}];
}
}
上面的代碼中其實(shí)會(huì)隱式地創(chuàng)建一個(gè) AutoreleasePool
,類似于:
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error {
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
@autoreleasepool { // 被隱式創(chuàng)建。
if (there is some error && error != nil) {
*error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
}
}
}];
// *error 在這里已經(jīng)被dict的做枚舉遍歷時(shí)創(chuàng)建的 Autorelease Pool釋放掉了。
}
}
為了能夠正常的使用 *error
,我們需要一個(gè) strong
類型的臨時(shí)引用,在 dict
的枚舉 Block
中是用這個(gè)臨時(shí)引用,保證引用指向的對(duì)象不會(huì)在出了 dict
的枚舉 Block
后被釋放,正確的方式如下:
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error {
NSError * __block tempError; // 加 __block 保證可以在Block內(nèi)被修改。
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (there is some error) {
*tempError = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
}
}]
if (error != nil) {
*error = tempError;
}
}