ARC 的__autoreleasing相關(guān)知識點(diǎn)

引子

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。

問題來了:

  1. -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;中為什么需要用 __autoreleasing 變量限定符修飾盗飒?
  2. 本地變量聲明( __strong)和參數(shù)( __autoreleasing)之間的區(qū)別導(dǎo)致編譯器創(chuàng)建臨時變量?

要解決上述兩個問題陋桂,首先得知道

  1. __autoreleasing是什么逆趣?
  2. __autoreleasing作用什么?(為什么要使用__autoreleasing
  3. __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é)上可知:

  1. 不能顯示的調(diào)用dealloc疾层,實(shí)現(xiàn)或調(diào)用 retain, release贡避, retainCount痛黎,或 autorelease

  2. 不能使用 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 }

通過上述的對比可知:

  1. 可通過 @autoreleaepool塊 來替代 NSNSAutoreleasePool類對象的生成杀捻、持有井厌、以及廢棄;

    1. ARC 的第1行代碼 與 MRC 的第1行代碼等價(jià)
    2. ARC 的第2行代碼 與 MRC 的第2和3行代碼等價(jià)
    3. ARC 的第3行代碼 與 MRC 的第4行代碼等價(jià)
    
  2. 通過將對象賦值給帶有 __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個例子
  1. 編譯器會檢查方法名是否以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;
  1. 雖然__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 被銷毀之前都能確保該對象存在昧绣。

  1. 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)存管理原則:

  1. 方法- (BOOL)performOperationWithError:(NSError *__autoreleasing *)error;不以alloc/new/copy/mutableCopy開頭的粘秆;
  2. 聲明為NSError *__autoreleasing *類型的 error 作為 *error被賦值墩朦;

以上兩點(diǎn)都符合“非自己生成并持有對象”規(guī)則,所以 error 取得是:注冊到 autoreleasepool翻擒,并取得非自己生成并持有的對象;

三牛哺、__autoreleasing使用注意點(diǎn)有哪些陋气?

  1. 對象指針賦值時,所有權(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; //此處為對象賦值
  1. (參照以上3種不顯式使用__autoreleasing坤溃,對象同樣被注冊到 autoreleasepool 的3個例子)雖然可以非顯式使用__autoreleasing修飾符拍霜,但在顯式地指定__autoreleasing修飾符時,必須要注意對象變量只能為自動變量(包括:局部變量薪介、函數(shù)以及方法參數(shù))

Ref

apple doc:

  1. Using Autorelease Pool Blocks
  2. Transitioning to ARC Release Notes

other doc:

  1. Objective-C 中的內(nèi)存分配
  2. 文檔翻譯-Using Autorelease Pool Blocks
  3. 理解AutoRelease Pool
  4. 遷移至ARC版本說明(Transitioning to ARC Release Notes)
  5. 《Obejctive-C 高級編程 iOS 與 OS X 多線程和內(nèi)存管理》
  6. NSError __autoreleasing error 中的 __autoreleasing 修飾符
  7. 黑幕背后的Autorelease
  8. [Objective-C 內(nèi)存管理——你需要知道的一切]
  9. Autorelease 詳解
  10. Autorelease Pool學(xué)習(xí)筆記
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祠饺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子汁政,更是在濱河造成了極大的恐慌道偷,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烂完,死亡現(xiàn)場離奇詭異试疙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)抠蚣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進(jìn)店門祝旷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事怀跛【啻” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵吻谋,是天一觀的道長忠蝗。 經(jīng)常有香客問我,道長漓拾,這世上最難降的妖魔是什么阁最? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮骇两,結(jié)果婚禮上速种,老公的妹妹穿的比我還像新娘。我一直安慰自己低千,他們只是感情好配阵,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著示血,像睡著了一般棋傍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上难审,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天瘫拣,我揣著相機(jī)與錄音,去河邊找鬼剔宪。 笑死拂铡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葱绒。 我是一名探鬼主播感帅,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼地淀!你這毒婦竟也來了失球?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤帮毁,失蹤者是張志新(化名)和其女友劉穎实苞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烈疚,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡黔牵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了爷肝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猾浦。...
    茶點(diǎn)故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡陆错,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出金赦,到底是詐尸還是另有隱情音瓷,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布夹抗,位于F島的核電站绳慎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏漠烧。R本人自食惡果不足惜杏愤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望已脓。 院中可真熱鬧声邦,春花似錦、人聲如沸摆舟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恨诱。三九已至,卻和暖如春骗炉,著一層夾襖步出監(jiān)牢的瞬間照宝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工句葵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厕鹃,地道東北人。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓乍丈,卻偏偏與公主長得像剂碴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子轻专,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評論 2 350

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