ARC下的autorelease

發(fā)現(xiàn)我從接觸iOS開發(fā)到現(xiàn)在,幾乎都沒有使用過autorelease這個詞。在ARC內存管理方式下,就像不能發(fā)送releaseretain消息一樣,程序員也不能對某個對象發(fā)送autorelease消息,讓我?guī)缀跬浟怂拇嬖凇K越裉焱蝗幌肫饋恚瑧摬椴锳RC下autorelease的正確使用姿勢(?_?)


內存管理原則



在內存管理方面,cocoa設立了一些基本原則,其中有這樣一條:

You own any object you create
You create an object using a method whose name begins with “alloc”, “new”, “copy”, or “mutableCopy”.

用以alloc/new/copy/mutableCopy為前綴的方法名創(chuàng)建的對象,是自己創(chuàng)建并持有的。

這條原則在ARC和MRC下都需要遵守。

比如cocoa中有很多工廠方法,它們不以alloc/new/copy/mutableCopy開頭,所以按照約定,它們返回的對象不應該被持有。在MRC下,一種可能的實現(xiàn)方式大概是這樣:

/*
 * MRC,此段代碼摘抄自[《Objective-C 高級編程》](https://book.douban.com/subject/24720270/)
 */

- (id)object
{
    id obj = [[NSObject alloc] init];

    /*
     * 自己持有對象
     */

    [obj autorelease];

    /*
     * 取得對象的存在,但自己不持有對象
     */

    return obj;
}

但是在ARC中,程序員不能對一個對象發(fā)送autorelease消息,那么如何將一個對象注冊到autorelease pool中,又如何遵循以上的這個原則呢?看來,開啟ARC后,編譯器幫助我們做了不少事情。


__autoreleasing修飾符



切換到ARC之后,每個指向OC對象的指針,都被賦上了所有權修飾符。一共有__strong__weak__unsafe_unretained__autoreleasing這樣四種所有權修飾符。

當一個對象被賦值給一個使用__autoreleasing修飾符修飾的指針時,相當于這個對象在MRC下被發(fā)送了autorelease消息,也就是說它被注冊到了autorelease pool中。

全局變量和實例變量是無法用__autoreleasing來修飾的,不然編譯器會報錯:

而局部變量用__autoreleasing修飾后,其指向的對象,在當前autorelease pool結束之前不會被回收:

__weak NSObject *weakObj1;
__weak NSObject *weakObj2;

{
    __autoreleasing NSObject *obj1 = [[NSObject alloc] init];
    weakObj1 = obj1; //weakObj1指向的對象已被注冊到autorelease pool
    
    __strong NSObject *obj2 = [[NSObject alloc] init];
    weakObj2 = obj2;//weakObj2指向的對象沒有被注冊到autorelease pool
}
//局部變量obj1和obj2的作用域結束,
//此時weakObj2指向的對象不再被強引用,因此被回收;
//而obj1指向的對象仍然在autorelease pool中

NSLog(@"%@", weakObj1);//輸出<NSObject: 0x100206030>,因為此刻weakObj1在autorelease pool中,不會因為obj1作用域的結束而被回收
NSLog(@"%@", weakObj2);//輸出null

方法名檢查



寫這樣一段代碼:

@interface XSQObject : NSObject

+ (NSString *)newHelloWorldString;
+ (NSString *)helloWorldString;

@end

@implementation XSQObject

+ (NSString *)newHelloWorldString {
    return [[NSString alloc] initWithCString:"HelloWorld" encoding:NSUTF8StringEncoding];
}

+ (NSString *)helloWorldString {
    return [[NSString alloc] initWithCString:"HelloWorld" encoding:NSUTF8StringEncoding];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __weak NSString *helloWorldString = [XSQObject helloWorldString];
        __weak NSString *newHelloWorldString = [XSQObject newHelloWorldString];
        //此處有warning: 
        //assigning retained object to weak variable; 
        //object will be released after assignment 

        NSLog(@"%@", helloWorldString);//輸出HelloWorld
        NSLog(@"%@", newHelloWorldString);//輸出null
        
    }
    return 0;
}

雖然[XSQObject helloWorldString][XSQObject newHelloWorldString]兩個方法的實現(xiàn)完全一樣,但是它們返回的對象被賦值給__weak指針后,前者仍然存在,而后者則被銷毀了。如果再加入@autorelease塊做點實驗,可以發(fā)現(xiàn)helloWorldString指向的對象其實已被注冊到autorelease pool中。

對比內存管理原則,這就像是在MRC下,不以alloc/new/copy/mutableCopy開頭的方法,會對返回的對象發(fā)送autorelease消息一樣。而事實上,在ARC下,編譯器會檢查方法名是否以alloc/new/copy/mutableCopy開頭,如果不是,則自動將返回的對象注冊到autorelease pool中。

更接近底層一點,其實Clang使用了一些標識來決定一個方法的返回值是否應該被持有。比如Clang文檔中有這樣的說明:

Methods in the alloc, copy, init, mutableCopy, and new families are implicitly marked __attribute__((ns_returns_retained)).

alloc/copy/init/mutableCopy/new家族中的方法,會被隱式標記為__attribute__((ns_returns_retained))

在一些特殊的情況下,程序員也可以手動給某些方法加上其他標記,來覆蓋被編譯器隱式加上的標記。


id *obj的傳遞



上面說到,編譯器會檢查方法名是否以alloc/new/copy/mutableCopy開頭,來判斷是否需要將返回的對象注冊到autorelease pool中。

函數(shù)之間如果想要傳遞一個對象,不僅可以通過返回值,也可以通過將一個id指針作為參數(shù)的方式。在cocoa框架中,NSError的對象經(jīng)常通過這種方式來傳遞,比如NSManagedObjectContext中的:

- (BOOL)save:(NSError **)error;

其實,這個(NSError **)error相當于(NSError * __autoreleasing *)error,編譯器默認為其生成了__autoreleasing修飾符。

編譯器默認生成__autoreleasing修飾符的做法,也是在貫徹內存管理原則,即確保只有通過以alloc/new/copy/mutableCopy開頭的方法返回的對象才能被持有。

雖然當我們自己定義id *obj類型的參數(shù)時,也可以顯式指定它的所有權修飾符為其他,并通過編譯,但為了貫徹內存管理原則,還是應該將id *obj類型的參數(shù)的所有權修飾符指定為__autoreleasing


參考

《Objective-C 高級編程》
Clang
Memory Management Policy
objc arc的簡單探索


2016.03.24更新



老板看了這篇后,問我,為什么在“方法名檢查”中用了initWithCString:encoding:來創(chuàng)建NSString對象?

哈哈,這是因為我試了別的幾種創(chuàng)建NSString對象的方式后,發(fā)現(xiàn)實驗結果和我預期的不符。

所以我來解釋一下為什么用下面幾種方法創(chuàng)建NSString對象,會得到與預期不符的實驗結果。

  1. literal

    literal,就是指用@"xxx"的方式創(chuàng)建一個NSString對象。然而,literal的內存管理方式有些特別。
    官方文檔明確指出了使用literal創(chuàng)建的NSString對象在整個程序的生命周期內都是不會被銷毀的:

    Objective-C string constant is created at compile time and exists throughout your program’s execution. The compiler makes such object constants unique on a per-module basis, and they’re never deallocated.

    所以如果用literal的方式來創(chuàng)建NSString對象,這樣得到的helloWorldStringnewHelloWorldString到輸出這一步時都不會變成空。

  2. 使用initWithString:

    既然literal沒法達到預期實驗效果,那用initWithString:方法應該能創(chuàng)建出比較正常的NSString對象了吧?
    helloWorldStringnewHelloWorldString兩個方法中的語句都替換為:

return [[NSString alloc] initWithString:@"HelloWorld"];

然而運行一下發(fā)現(xiàn),newHelloWorldString并沒有像預期的一樣被銷毀。

這是什么原因呢?嗯。。如果換一種寫法:
NSString *s1 = @"Hello World";
NSString *s2 = [[NSString alloc] initWithString:s1];

然后運行到這里時輸出s1s2指向的地址,發(fā)現(xiàn)這兩個指針竟然指向同一個地址。看來initWithString:方法中可能做了某些處理?

  1. 使用stringWithFormat:

    老板問我用stringWithFormat:為什么不能達到預期效果,雖然我當場有點懵逼,但是回頭一想,這不是顯然么。
    因為stringWithFormat:這個方法,不是以alloc/new/copy/mutableCopy開頭的呀,所以它返回的對象已經(jīng)被注冊到了autorelease pool里呀。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容