引子
apple 的《Transitioning to ARC Release Notes》一文里,其中一小節(jié)"ARC Introduces New Lifetime Qualifiers"(ARC 引入新的生命周期限定符)中對 Variable Qualifiers(變量限定符)的說明放接,有一段代碼引起了我的注意:(NSError * __autoreleasing *)error
翻譯過來是:
你也要注意通過引用方式傳入的對象冈欢。以下代碼會正常運(yùn)轉(zhuǎn)NSError *error; BOOL OK = [myObject performOperationWithError:&error]; if (!OK) { // Report the // ...
而 error 的聲明是隱式的:
NSError * __strong e;
方法的聲明通常是:
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
因此編譯器會重寫代碼:
NSError * __strong error; NSError * __autoreleasing tmp = error; BOOL OK = [myObject performOperationWithError:&tmp]; error = tmp; if (!OK) { // Report the error. // ...
本地變量聲明( __strong)和參數(shù)( __autoreleasing)之間的區(qū)別導(dǎo)致編譯器創(chuàng)建臨時變量椿胯。在獲取__strong變量的地址時你可以通過將參數(shù)聲明為 id __storng*來獲得其原始指針。或者你可以將變量聲明為 __autoreleasing。
問題來了:
- -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;中為什么需要用 __autoreleasing 變量限定符修飾盗飒?
- 本地變量聲明( __strong)和參數(shù)( __autoreleasing)之間的區(qū)別導(dǎo)致編譯器創(chuàng)建臨時變量?
要解決上述兩個問題陋桂,首先得知道
- __autoreleasing是什么逆趣?
- __autoreleasing作用什么?(為什么要使用__autoreleasing)
- __autoreleasing使用注意點(diǎn)有哪些嗜历?
一宣渗、__autoreleasing 是什么?
從apple 的《Transitioning to ARC Release Notes》一文中梨州,
"ARC Introduces New Lifetime Qualifiers"(ARC 引入新的生命周期限定符)里對 Variable Qualifiers(變量限定符)的說明 痕囱;
小節(jié)上可知:
__autoreleasing 是 ARC 下用于控制變量生命周期而引入的4個變量限定符之一。
- 四個變量限定符為:
Variable Qualifier | Desc | other |
---|---|---|
__strong | 是默認(rèn)的暴匠。只要有強(qiáng)類型指針指向一個對象咐蝇,那么該對象會一直”生存“下去。 | |
__weak | 表明一個不會維持所持對象生命期的引用。當(dāng)沒有強(qiáng)引用指向該對象時有序,弱引用會設(shè)置為nil。 | |
__unsafe_unretained | 指定一個引用岛请,該引用不會維持所持對象的生命期旭寿,并且在沒有強(qiáng)引用指向?qū)ο髸r也不會設(shè)置為nil。如果它所指向的對象已經(jīng)被釋放崇败,那么它會成為一個野指針盅称。 | |
__autoreleasing | 用以指示以引用(id*)傳入的參數(shù)并在retun后自動釋放。 |
二后室、__autoreleasing 作用什么缩膝?(為什么要使用__autoreleasing)
從apple 的《Transitioning to ARC Release Notes》一文中,
ARC Enforces New Rules(ARC強(qiáng)制執(zhí)行新規(guī)則)岸霹;
小節(jié)上可知:
不能顯示的調(diào)用dealloc疾层,實(shí)現(xiàn)或調(diào)用 retain, release贡避, retainCount痛黎,或 autorelease。
-
不能使用 NSAutoreleasePool 對象
ARC 提供了 @autoreleasepool來代替刮吧。這比 NSAutoreleasePool更高效湖饱。
對比一下 MRC 與 ARC 下使用 autoreleasepool 的不同地方:
/* MRC */
1 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2 id obj = [[NSObject alloc] init];
3 [obj autorelease]; //對象調(diào)用 autorelease 方法就是將該對象注冊到最近的 autoreleasepool 中
4 [pool drain];
而 ARC 下,可將上述代碼寫成下面這樣:
/* ARC */
1 @autoreleasepool {
2 id __autoreleasing obj = [[NSObject alloc] init];
3 }
通過上述的對比可知:
-
可通過 @autoreleaepool塊 來替代 NSNSAutoreleasePool類對象的生成杀捻、持有井厌、以及廢棄;
1. ARC 的第1行代碼 與 MRC 的第1行代碼等價(jià) 2. ARC 的第2行代碼 與 MRC 的第2和3行代碼等價(jià) 3. ARC 的第3行代碼 與 MRC 的第4行代碼等價(jià)
通過將對象賦值給帶有 __autoreleasing 修飾符的變量來代替調(diào)用 autorelease 方法致讥,即將對象注冊到 autoreleasepool仅仆;
因?yàn)椴荒苁褂?NSAutoreleasePool 和 調(diào)用 autorelease方法了,所以在給出一個對應(yīng)的替換方案
使用@autoreleaepool塊
對應(yīng) 使用NSAutoreleasePool
拄踪;
使用__autoreleasing
修飾符對應(yīng) 調(diào)用autorelease
方法
但是實(shí)際情況是:通常情況下不會顯式地使用 __autoreleasing修飾符蝇恶,就像不會顯式地使用 __strong修飾符一樣;
如下就是不顯式使用__autoreleasing
但對象同樣被注冊到 pool的3個例子
- 編譯器會檢查方法名是否以alloc, new, copy, mutableCopy 開始惶桐,如果不是則自動將返回值的對象注冊到 autoreleasepool 中撮弧;
《Obejctive-C 高級編程 iOS 與 OS X 多線程和內(nèi)存管理》一書中對內(nèi)存的管理思想描述如下:
自己生成的對象,自己所持有姚糊。 非自己生成的對象贿衍,自己也能持有。(MRC:讓對象調(diào)用 retain 方法救恨,ARC:有__strong限定符) 不需要自己持有的對象是釋放贸辈。 非自己持有的對象無法釋放。
判斷是否自己生成的對象肠槽,自己所持有規(guī)則有
1. 使用 alloc, new, copy, mutableCopy 開頭的方法意味著:自己生成的對象擎淤,自己持有奢啥。 2. 使用 alloc, new, copy, mutableCopy 以外的方法取得的對象為:非自己生成并持有。
基于上述的理解后嘴拢,下面代碼中木有顯式調(diào)用__autoreleasing
桩盲,但生成的對象同樣被注冊到 pool 中:
@autoreleasepool {
id __strong obj = [NSMutableArray array];
}
因?yàn)镹SMutableArray的類方法array
不是使用 alloc, new, copy, mutableCopy 開頭的方法,產(chǎn)生的對象在編譯判斷方法名后自動注冊到 autoreleasepool 中席吴。
這個描述已不太正確的赌结,但就目前文章要解決的問題來說足夠了,相關(guān)描述請參見《理解AutoRelease Pool》的最后一節(jié)“最后一個問題”孝冒,這也是編譯做了優(yōu)化導(dǎo)致的情況之一)
以下為“取得非自己生成并持有對象”的方法(+array)事例代碼
+ (instancetype)array {
return [NSMutableArray array];
}
上述代碼可寫成:
+ (instancetype)array {
id obj = [NSMutableArray array];
return obj;
}
在 ARC 下柬姚,因?yàn)開_strong 為默認(rèn)修飾符,所以 id obj 與 id __strong obj 等價(jià)的庄涡。由于 return 使得對象變量超出其變量作用域量承,所以該強(qiáng)引用對應(yīng)的自己持有的對象會被自動釋放,**但該對象作為函數(shù)的返回值啼染,編譯器會自動將其注冊到 autoreleasepool 中宴合。
那大家思考一下如下這段代碼:
@autoreleasepool {
id obj = [[NSMutableArray alloc] init];
}
//上面init的對象是否被添加到 autoreleasepool 中?
我自己的答案是:(如有錯誤歡迎指正)
被添加到autoreleasepool了迹鹅,默認(rèn)情況下要將 obj 指向的對象添加到autoreleasepool中是需要 __autoreleasing 修飾符去修飾 obj 的卦洽,那么ARC 下它應(yīng)該就會進(jìn)行一個編譯轉(zhuǎn)換,如:
id __autoreleasing tem = obj;
- 雖然__weak 修飾符是為了避免強(qiáng)引用循環(huán)(strong reference circle)而使用的斜棚,但在訪問附有 __weak 修飾符的變量時阀蒂,實(shí)際上必定要訪問注冊到 autoreleasepool 的對象。
id __weak obj1 = obj0;
NSLog(@"class = %@", [obj1 class]);
以下源碼與上面相同
id __weak obj1 = obj0;
id __autoreleasing tem = obj1;
NSLog(@"class = %@", [tem class]);
因?yàn)?__weak 修飾符只持有對象的弱引用弟蚀,而在訪問引用對象過程中蚤霞,該對象有可能被廢棄。而將弱引用對象注冊到 autoreleasepool 中义钉,在 pool 被銷毀之前都能確保該對象存在昧绣。
- id 的指針或?qū)ο蟮闹羔樤跊]有顯式地指定修飾符時候,會被默認(rèn)附加上__autoreleasing 修飾符捶闸;
如使用附有 __autoreleasing 修飾符的變量作為對象獲得參數(shù)夜畴,與除 alloc/new/copy/mutableCopy外其他方法的返回值取得對象完全一樣,都會注冊到 autoreleasepool删壮,并取得非自己生成并持有的對象贪绘。
有些情況下,我們是通過引用方式傳入的對象央碟,以獲得更詳細(xì)的情況税灌,如:為了得到詳細(xì)的錯誤信息,經(jīng)常會在方法的參數(shù)中傳遞 NSError 對象指針
+ (nullable instancetype)stringWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError **)error;
實(shí)際編譯的時候,該方法中的error 參數(shù)會附加上__autoreleasing 修飾符
+ (nullable instancetype)stringWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError * __autoreleasing *)error;
假設(shè)我們有一個方法,方法聲明如下
- (BOOL)performOperationWithError:(NSError **)error;
//實(shí)際在編譯時是這樣的: - (BOOL)performOperationWithError:(NSError *__autoreleasing *)error;
該方法接受一個 NSError 對象的指針作為參數(shù)菱涤,返回一個 BOOL 值苞也。使用時如下所示:
NSError *error = nil;
BOOL result = [obj performOperationWithError:&error];
假設(shè)當(dāng)發(fā)生錯誤時,方法內(nèi)部動作如下:
- (BOOL)performOperationWithError:(NSError **)error {
// 方法執(zhí)行發(fā)生錯誤
*error = [NSError errorWithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}
對比之前提到的內(nèi)存管理原則:
- 方法
- (BOOL)performOperationWithError:(NSError *__autoreleasing *)error;
不以alloc/new/copy/mutableCopy開頭的粘秆; - 聲明為
NSError *__autoreleasing *
類型的 error 作為*error
被賦值墩朦;
以上兩點(diǎn)都符合“非自己生成并持有對象”規(guī)則,所以 error 取得是:注冊到 autoreleasepool翻擒,并取得非自己生成并持有的對象;
三牛哺、__autoreleasing使用注意點(diǎn)有哪些陋气?
- 對象指針賦值時,所有權(quán)修飾符必須一致
//編譯錯誤
NSError *error = nil;
NSError **pError = &error;
//編譯正確
NSError *error = nil; // 默認(rèn)修飾符是 __strong
NSError *__strong *pError = &error;
同樣的:
NSError __weak *error = nil;
NSError *__weak *pError = &error;//編譯正確
NSError __unsafe_unretain *error = nil;
NSError *__unsafe_unretain *pError = &error;//編譯正確
按照上述引润,為啥下面的代碼能夠正常運(yùn)行巩趁?
NSError *error1 = nil;
BOOL OK = [myObject performOperationWithError:&error];
例子中聲明的是 NSError *error = nil; 默認(rèn)修飾符 __strong
, 而- (BOOL)performOperationWithError:(NSError *__autoreleasing *)error2;
方法參數(shù)中使用的是__autoreleasing
淳附,傳值時是 error2 = &error1
议慰,對象指針賦值,但所有權(quán)修飾符不一致奴曙,為什么沒有報(bào)錯呢别凹?
這里就由文章開頭的問題2說明了,針對這種“通過引用方式傳入的對象”洽糟,編譯器會重寫代碼炉菲,也就是本地變量聲明( __strong)和參數(shù)( __autoreleasing)之間的區(qū)別導(dǎo)致編譯器創(chuàng)建臨時變量
:
NSError *error = nil;
NSError __autoreleasing *tmp = error; //此處為對象賦值
BOOL result = [obj performOperationWithError:&tmp];//傳參時的動作才是對象指針賦值,需要保證所有權(quán)修飾符一致
error = tmp; //此處為對象賦值
- (參照以上3種不顯式使用
__autoreleasing
坤溃,對象同樣被注冊到 autoreleasepool 的3個例子)雖然可以非顯式使用__autoreleasing修飾符拍霜,但在顯式地指定__autoreleasing修飾符時,必須要注意對象變量只能為自動變量(包括:局部變量薪介、函數(shù)以及方法參數(shù))
Ref
apple doc:
other doc:
- Objective-C 中的內(nèi)存分配
- 文檔翻譯-Using Autorelease Pool Blocks
- 理解AutoRelease Pool
- 遷移至ARC版本說明(Transitioning to ARC Release Notes)
- 《Obejctive-C 高級編程 iOS 與 OS X 多線程和內(nèi)存管理》
- NSError __autoreleasing error 中的 __autoreleasing 修飾符
- 黑幕背后的Autorelease
- [Objective-C 內(nèi)存管理——你需要知道的一切]
- Autorelease 詳解
- Autorelease Pool學(xué)習(xí)筆記