本文是 「iOS 開發(fā):徹底理解 iOS 內(nèi)存管理」系列的「ARC 篇」谚中。
用來對 Objective-C 語法中拧晕,自動管理內(nèi)存 ARC 相關(guān)知識進行講解辫樱。
1. 簡介
Automatic Reference Counting,自動引用計數(shù),即 ARC带膀,WWDC 2011 和 iOS 5 所引入的最大的變革和最激動人心的變化装蓬。ARC 是新的 LLVM 3.0 編譯器的一項特性捡偏,使用 ARC,可以說一 舉解決了廣大 iOS 開發(fā)者所憎恨的手動內(nèi)存管理的麻煩帅容。
使用 ARC 后颇象,系統(tǒng)會檢測出何時需要保持對象,何時需要自動釋放對象丰嘉,何時需要釋放對象夯到,編譯器會管理好對象的內(nèi)存,會在何時的地方插入
retain
饮亏、release
和autorelease
耍贾,通過生成正確的代碼去自動釋放或者保持對象。我們完全不用擔(dān)心編譯器會出錯路幸。
2. ARC 所有權(quán)修飾符
「引用計數(shù)式內(nèi)存管理」的本質(zhì)部分在 ARC 中并沒有改變荐开,ARC 只是自動幫我們處理了「引用計數(shù)」的相關(guān)部分。
為了處理對象简肴,ARC 引入了以下四種變量所有權(quán)修飾符晃听。
-
__strong
:強指針,默認所有對象的指針變量都是強指針類型砰识。只要還有一個強指針指向某個對象能扒,則這個對象就會一直存活。 -
__weak
:弱指針辫狼,不能持有對象實例初斑。如果一個對象沒有強指針引用,則弱指針引用會被置為 nil膨处。 -
__unsafe_unretained
:和__weak
相似见秤,是一種弱引用關(guān)系砂竖。區(qū)別在于如果一個對象沒有強指針引用,則__unsafe_unretained
引用不會被置為 nil鹃答,而是會變成一個野指針乎澄。
__autoreleasing
:用于通過引用傳遞對象,指示以引用(id*
)傳入的參數(shù)并在函數(shù)返回時自動釋放测摔。
2.1 __strong
修飾符
默認所有對象的所有權(quán)修飾符都是強指針類型置济。也就是說:
id obj = [NSObject alloc] init];
等同于:
id __strong obj = [NSObject alloc] init];
其對應(yīng)的內(nèi)存管理過程如下:
{
id __strong obj = [NSObject alloc] init]; //obj 自己生成并持有對象
} // obj 超過作用域,強引用失效锋八,將會自動釋放所持有的對象
2.2 __weak
修飾符
__weak
修飾符大多用來解決引用計數(shù)式內(nèi)存管理中的「循環(huán)引用」問題的舟肉。如果兩個以上的成員變量互相強引用對方,則兩個對象將永遠不會被釋放查库,從而發(fā)生內(nèi)存泄漏路媚。所謂內(nèi)存泄露就是當廢棄的對象在超出其生存周期后繼續(xù)存在。
舉個例子樊销,比如下邊的 Test 類整慎,生成兩個實例對象 test0、test1围苫,通過 setOject:
方法裤园,造成了相互引用:
@interface Test : NSObject
{
id __strong obj_;
}
- (void)setObject: (id __strong)obj;
@end
@implementation Test
- (id)init {
self = [super init];
return self;
}
- (void)setObject: (id __strong)obj {
obj_ = obj;
}
@end
id test0 = [[Test alloc] init]; // test0 生成并持有對象 A
id test1 = [[Test alloc] init]; // test1 生成并持有對象 B
[test0 setObject: test1]; // test0 強引用對象 B
[test1 setObject: test0]; // test1 強引用對象 A
因為alloc
方法和 setObject
方法都是強引用,所以會出現(xiàn)兩個對象互相強引用對方的情況剂府。
可以使用 __weak
修飾符消除循環(huán)引用拧揽。因為帶 __weak
修飾符的變量不持有對象,所以在超出其變量作用域時腺占,對象就會被釋放淤袜。
@interface Test:NSObject
{
id __weak obj_;
}
- (void)setObject:(id __strong)obj;
@end
2.3 __unsafe_unretained
修飾符
就像上邊提到的那樣,__unsafe_unretained
和 __weak
相似衰伯,是一種弱引用關(guān)系铡羡。區(qū)別在于如果一個對象沒有強指針引用,則 __unsafe_unretained
引用不會被置為 nil意鲸,而是會變成一個野指針烦周。
那有了 __weak
读慎,為什么還有 __unsafe_unretained
呢?
__unsafe_unretained
主要是跟 C 語言代碼相互闰靴。此外,__weak
會消耗一定的性能,使用 __weak
需要檢查對象是否被釋放淑翼,在追蹤是否被釋放的時候需要追蹤一些信息,則使用 __unsafe_unretained
比 __weak
快遭京,消耗 CPU 資源也比 __weak
少。
而且一個對象有大量的 __weak
引用對象的時候,當對象被釋放,那么此時就要遍歷 weak
表苦始,把表里所有的指針置空,消耗 CPU 資源。
綜上所述,當明確知道對象的生命期時冀膝,選擇 __unsafe_unretained
會有一些性能提升。但是 __unsafe_unretained
也容易引發(fā)野指針問題赐纱。
2.4 __autoreleasing
修飾符
在 ARC 模式下,我們不能顯示的使用 autorelease
方法了起胰,但是 autorelease
的機制還是有效的,我們可以通過將對象賦給 __autoreleasing
修飾的變量,就能達到在 MRC 模式下調(diào)用對象的 autorelease
方法同樣的效果瓜客。
附有 __autoreleasing
修飾的變量不是局部變量否彩,它的生命周期由autoreleasepool
負責(zé)敬尺,在 @autoreleasepool
結(jié)束之前都能確保該對象存在。
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
上述代碼主要將 NSObject 類對象注冊到 autoreleasepool
線程池中蜻直,其模擬器源碼如下:
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
可以看出:__autoreleasing
修飾的對象會被注冊到 Autorelease Pool 中王悍,并在 Autorelease Pool 銷毀時被釋放晋辆,和 MRC 特性下的 autorelease
的意義相同。
3. ARC 的使用
在 MRC 的時代宇整,我們需要自己調(diào)用 retain
方法去持有一個對象瓶佳,而現(xiàn)在不需要的。我們唯一需要做的是使用一個指針指向這個對象鳞青,只要這個指針沒有被置空霸饲,對象就會一直保持在堆上。當我們將指針指向新的對象時臂拓,原來的對象就會被 release
一次厚脉。具體用法如下:
int main(int argc, const char * argv[]) {
// 不用寫 release, main 函數(shù)執(zhí)行完畢后 p 會被自動釋放
Person *p = [[Person alloc] init];
return 0;
}
p
指針現(xiàn)在指向 Person 對象,此時這個對象(Person 類生成的對象)將會被 p 指針強引用胶惰,此時 p
就持有了這個對象傻工。
直到 main 函數(shù)執(zhí)行完畢,Person 類生成的對象超出了作用范圍的空間孵滞,此時 p
也不再持有該對象中捆,該對象也即將被銷毀,內(nèi)存得到釋放坊饶。
4. ARC 的使用規(guī)則
- 不能使用
retain
/release
/retainCount
/autorelease
泄伪,使用會導(dǎo)致編譯器報錯。 - 不能使用
NSAllocateObject
/NSDeallocateObject
匿级,使用會導(dǎo)致編譯器報錯蟋滴。 - 對象的生成/持有的方法必須遵循以下命名規(guī)則:
alloc
/new
/copy
/mutableCopy
/init
。 - 不能顯式調(diào)用
dealloc
痘绎。重寫父類的dealloc
方法時脓杉,不能再調(diào)用[super dealloc];
。 - 使用
@autorelease
塊代替 NSAutoreleasePool简逮。
5. ARC 下單對象內(nèi)存管理
- 局部變量釋放后對象隨之被釋放:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
} // 執(zhí)行到這一行局部變量 p 釋放
// 由于沒有強指針指向?qū)ο? 所以對象也釋放
return 0;
}
- 清空指針后對象隨之被釋放:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
p = nil; // 執(zhí)行到這一行, 由于沒有強指針指向?qū)ο? 所以對象被釋放
}
return 0;
}
- 默認情況下所有指針都是強指針
int main(int argc, const char * argv[]) {
@autoreleasepool {
// p1 和 p2 都是強指針
Person *p1 = [[Person alloc] init];
__strong Person *p2 = [[Person alloc] init];
}
return 0;
}
- 弱指針使用注意:千萬不要使用弱指針保存新創(chuàng)建的對象球散。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// p1 是弱指針,對象會被立即釋放
__weak Person *p1 = [[Person alloc] init];
}
return 0;
}
6. ARC 下多對象內(nèi)存管理
- ARC 和 MRC 一樣, 想擁有某個對象必須用強指針保存對象, 但是不需要在
dealloc
方法中調(diào)用release
散庶。
@interface Person : NSObject
// MRC 寫法
//@property (nonatomic, retain) Dog *dog;
// ARC 寫法
@property (nonatomic, strong) Dog *dog;
@end
7. ARC 下 @property 參數(shù)
- strong:表示指向并擁有該對象蕉堰。用于 OC 對象凌净,相當于 MRC 中的 retain。
- weak:表示指向但不擁有該對象屋讶。用于 OC 對象冰寻,相當于 MRC 中的 assign
- assign:用于修飾基本數(shù)據(jù)類型,跟 MRC 中的 assign 一樣皿渗,不涉及內(nèi)存管理斩芭。
- copy:與
strong
類似,不同之處在于 copy 在對象進行賦值(調(diào)用setter
方法)時執(zhí)行的是copy
操作而不是retain
操作乐疆。
這里說一下 strong
和 copy
的區(qū)別划乖。
@property
參數(shù)會幫我們生成對應(yīng)的 setter
、getter
方法挤土。不同的修飾符生成的 setter
琴庵、getter
方法也不同。
strong
對應(yīng)的 setter
方法仰美,是將參數(shù)進行了 retain
操作迷殿,而 copy
對應(yīng)的 setter
方法,是將參數(shù)內(nèi)容進行了 copy
操作咖杂。
copy
操作在原對象是可變類型和不可變類型兩種不同情況下是有區(qū)別的:
- 當賦值參數(shù)為不可變類型(比如 NSString)時庆寺,在進行賦值操作時,
copy
操作跟strong
效果一樣诉字,只是對參數(shù)做了一次淺拷貝止邮,地址不變。 - 當賦值參數(shù)為可變類型(比如 NSMutableString)時奏窑,在進行賦值操作時导披,
strong
的指針還是指向原地址。而copy
操作則是對參數(shù)內(nèi)容做了一次深拷貝埃唯,生成了一個新的對象撩匕,地址發(fā)生了改變。
這樣墨叛,如果賦值參數(shù)為可變類型止毕,當賦值參數(shù)發(fā)生改變的時候,使用 strong
修飾的對象也會跟著改變漠趁,因為兩者指向的是同一個地址扁凛。而使用 copy
修飾的對象則不會跟著改變,這是因為 copy
指針指向的是一個新的對象闯传。
所以 copy
多用于修飾帶有可變類型的不可變對象上(NSString / NSArray / NSDictionary)谨朝。這是為了避免可變類型數(shù)據(jù)賦值給不可變類型數(shù)據(jù)時,內(nèi)容發(fā)生改變的情況。
8. ARC 下循環(huán)引用問題
- ARC 和 MRC 一樣字币,如果 A 擁有 B则披,B 也擁有 A,那么必須一方使用弱指針洗出。
@interface Person : NSObject
@property (nonatomic, strong) Dog *dog;
@end
@interface Dog : NSObject
// 錯誤寫法, 循環(huán)引用會導(dǎo)致內(nèi)存泄露
//@property (nonatomic, strong) Person *owner;
// 正確寫法, 當如果保存對象建議使用 weak
@property (nonatomic, weak) Person *owner;
@end