淺談 iOS ARC 內(nèi)存管理

Objective-C 采用的是引用計(jì)數(shù)式的內(nèi)存管理方式:

  • 自己生成的對象自己持有。

  • 非自己生成的對象自己也能持有钱床。

  • 自己持有的對象不再需要時(shí)釋放祝谚。

  • 非自己持有的對象自己無法釋放氮凝。

  • 使用以下名稱開頭的方法名意味著自己生成的對象只有自己持有

  • alloc

  • new

  • copy

  • mutableCopy

/*
 * 自己生成并持有該對象
 */
 id obj0 = [[NSObeject alloc] init];
 id obj1 = [NSObeject new];
  • 非自己生成的對象陕凹,自己也能持有
/*
 * 持有非自己生成的對象
 */
id obj = [NSArray array]; // 非自己生成的對象,且該對象存在檩小,但自己不持有
[obj retain]; // 自己持有對象

備注:通過 retain 方法來讓指針變量持有這個(gè)新生成的對象开呐。

  • 不再需要自己持有的對象時(shí)釋放
/*
 * 不在需要自己持有的對象的時(shí)候,釋放
 */
id obj = [[NSObeject alloc] init]; // 此時(shí)持有對象
[obj release]; // 釋放對象
/*
 * 指向?qū)ο蟮闹羔樔跃捅槐A粼趏bj這個(gè)變量中
 * 但對象已經(jīng)釋放规求,不可訪問
 */

自己持有的對象筐付,一旦不再需要,持有者有義務(wù)釋放該對象阻肿。釋放使用 release 方法家妆。
當(dāng)調(diào)用對象的 release 方法只是將對象的引用計(jì)數(shù)器 -1,當(dāng)對象的引用計(jì)數(shù)器為 0 的時(shí)候會(huì)調(diào)用了對象的 dealloc 方法才能進(jìn)行釋放對象的內(nèi)存冕茅。

  • 非自己生成的對象持有對象的釋放
//非自己生成的對象伤极,暫時(shí)沒有持有
id obj = [NSMutableArray array];

//通過retain持有對象
[obj retain];

//釋放對象
[obj release];

兩種不允許的情況:

  • 釋放自己不持有的對象
/*
 * 非自己持有的對象無法釋放
 */
id obj = [NSArray array]; // 非自己生成的對象蛹找,且該對象存在,但自己不持有
[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)廢棄了的對象
id obj = [[NSObject alloc] init];//持有新生成的對象
[obj doSomething];//使用該對象
[obj release];//釋放該對象当编,不再持有了
[obj release];//釋放已經(jīng)廢棄了的對象届慈,崩潰

autorelease

當(dāng)對象超出其作用域時(shí),對象實(shí)例的 release 方法就會(huì)被調(diào)用忿偷,autorelease 的具體使用方法如下:

  • 生成并持有 NSAutoreleasePool 對象金顿。
  • 調(diào)用已分配對象的 autorelease 方法。
  • 廢棄 NSAutoreleasePool 對象鲤桥。
- (id) getAObjNotRetain {
    id obj = [[NSObject alloc] init]; // 自己持有對象
    [obj autorelease]; // 取得的對象存在揍拆,但自己不持有該對象
    return obj;
}

這個(gè)特性是使用 autorelease 來實(shí)現(xiàn)的,autorelease 使得對象在超出生命周期后能正確的被釋放(通過調(diào)用 release 方法)茶凳。在調(diào)用 release 后嫂拴,對象會(huì)被立即釋放,而調(diào)用 autorelease 后贮喧,對象不會(huì)被立即釋放筒狠,而是注冊到 autoreleasepool 中,當(dāng) autoreleasepool 銷毀時(shí)箱沦,會(huì)對 autoreleasepool 里面的所有對象做一次 release 操作辩恼。

ARC 環(huán)境下,id 類型和對象類型和 C 語言其他類型不同谓形,類型前必須加上所有權(quán)的修飾符灶伊。
所有權(quán)修飾符總共有4種:

  • __strong
  • __weak
  • __autoreleasing
  • __unsafe_unretained

__strong

__strong 表示強(qiáng)引用,對應(yīng)定義 property 時(shí)用到的 strong套耕。當(dāng)對象沒有任何一個(gè)強(qiáng)引用指向它時(shí),它才會(huì)被釋放峡继。如果在聲明引用時(shí)不加修飾符冯袍,那么引用將默認(rèn)是強(qiáng)引用。當(dāng)需要釋放強(qiáng)引用指向的對象時(shí)碾牌,需要保證所有指向?qū)ο髲?qiáng)引用置為 nil康愤。__strong 修飾符是 id 類型和對象類型默認(rèn)的所有權(quán)修飾符。

__weak

__weak 表示弱引用舶吗,對應(yīng)定義 property 時(shí)用到的 weak征冷。弱引用不會(huì)影響對象的釋放,而當(dāng)對象被釋放時(shí)誓琼,所有指向它的弱引用都會(huì)自定被置為 nil检激,這樣可以防止野指針肴捉。__weak 最常見的一個(gè)作用就是用來避免強(qiáng)引用循環(huán)。

__weak 的幾個(gè)使用場景:

  • delegate 關(guān)系中防止強(qiáng)引用循環(huán)叔收。在 ARC 特性下齿穗,通常我們應(yīng)該設(shè)置 delegate 屬性為 weak 的。但是這里有一個(gè)疑問饺律,我們常用到的 UITableViewdelegate 屬性是這樣定義的:@property (nonatomic, assign) id<UITableViewDelegate> delegate;窃页,為什么用的修飾符是assign 而不是 weak?其實(shí)這個(gè) assignARC 中意義等同于 __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ì)被注冊到 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 修飾符變量所引用的對象并 retain, objc_autorelease 函數(shù)將對象放入 autoreleasePool 中妓雾,據(jù)此當(dāng)我們訪問 weak 修飾指針指向的對象時(shí),實(shí)際上是訪問注冊到自動(dòng)釋放池的對象垒迂。因此械姻,如果大量使用 weak 的話,在我們?nèi)ピL問 weak 修飾的對象時(shí)机断,會(huì)有大量對象注冊到自動(dòng)釋放池,這會(huì)影響程序的性能楷拳。

解決方案:
要訪問 weak 修飾的變量時(shí),先將其賦給一個(gè) strong 變量吏奸,然后進(jìn)行訪問欢揖。

為什么訪問 weak 修飾的對象就會(huì)訪問注冊到自動(dòng)釋放池的對象呢?

因?yàn)?weak 不會(huì)引起對象的引用計(jì)數(shù)器變化,因此奋蔚,該對象在運(yùn)行過程中很有可能會(huì)被釋放她混。所以,需要將對象注冊到自動(dòng)釋放池中并在 autoreleasePool 銷毀時(shí)釋放對象占用的內(nèi)存泊碑。

__autoreleasing

ARC 模式下坤按,我們不能顯示的使用 autorelease 方法了,但是 autorelease 的機(jī)制還是有效的馒过,通過將對象賦給 __autoreleasing 修飾的變量就能達(dá)到在 MRC 模式下調(diào)用對象的 autorelease 方法同樣的效果臭脓。

__autoreleasing 修飾的對象會(huì)被注冊到 Autorelease Pool 中,并在 Autorelease Pool 銷毀時(shí)被釋放腹忽。

注意:定義 property 時(shí)不能使用這個(gè)修飾符来累,因?yàn)槿魏我粋€(gè)對象的 property 都不應(yīng)該是 autorelease 類型的砚作。

__unsafe_unretained

ARC 是在 iOS5 引入的,而 __unsafe_unretained 這個(gè)修飾符主要是為了在 ARC 剛發(fā)布時(shí)兼容 iOS4 以及版本更低的系統(tǒng)佃扼,因?yàn)檫@些版本沒有弱引用機(jī)制偎巢。這個(gè)修飾符在定義 property 時(shí)對應(yīng)的是 unsafe_unretained__unsafe_unretained 修飾的指針純粹只是指向?qū)ο蠹嬉瑳]有任何額外的操作压昼,不會(huì)去持有對象使得對象的 retainCount +1。而在指向的對象被釋放時(shí)依然原原本本地指向原來的對象地址瘤运,不會(huì)被自動(dòng)置為 nil窍霞,所以成為了野指針,非常不安全拯坟。

__unsafe_unretained 的應(yīng)用場景:

  • 在 ARC 環(huán)境下但是要兼容 iOS4.x 的版本但金,用 __unsafe_unretained 替代 __weak 解決強(qiáng)引用循環(huán)的問題。

最后

總結(jié)郁季, autorelease 的機(jī)制卻依然在很多地方默默起著作用冷溃,我們來看看這些場景:

  • 方法返回值。
  • 訪問 __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)引用持有的對象本該被釋放,但是由于該對象作為函數(shù)返回值冗恨,所以一般情況下編譯器會(huì)自動(dòng)將其注冊到 AutoreleasePool 中(注意這里是一般情況下答憔,在一些特定情況下,ARC 機(jī)制提出了巧妙的運(yùn)行時(shí)優(yōu)化方案來跳過 autorelease 機(jī)制掀抹。)虐拓。

ARC 模式下方法返回值跳過 autorelease 機(jī)制的優(yōu)化方案

為什么方法返回值的時(shí)候需要用到 autorelease機(jī)制呢?

當(dāng)對象被作為參數(shù)返回 return 之后傲武,如果調(diào)用者需要使用就需要強(qiáng)引用它蓉驹,那么它 retainCount + 1,用完之后再清理谱轨,使它 retainCount - 1戒幔。

如果在方法中創(chuàng)建了對象并作為返回值時(shí)吠谢,根據(jù) ARC 內(nèi)存管理的原則土童,誰創(chuàng)建誰釋放。既然作為返回值工坊,就必須保證返回時(shí)對象沒被釋放以便方法外的調(diào)用者能拿到有效的對象献汗,否則你返回的是 nil敢订,有何意義呢。所以就需要找一個(gè)合理的機(jī)制既能延長這個(gè)對象的生命周期罢吃,又能保證對其釋放楚午。這個(gè)機(jī)制就是 autorelease 機(jī)制

ARC 模式下在方法 return 的時(shí)候尿招,會(huì)調(diào)用 objc_autoreleaseReturnValue()
方法替代 autorelease矾柜。在調(diào)用者強(qiáng)引用方法返回對象的時(shí)候,會(huì)調(diào)用 objc_retainAutoreleasedReturnValue() 方法就谜,該方法會(huì)去檢查該方法或者調(diào)用方的執(zhí)行命令列表怪蔑,是否會(huì)被傳給 objc_retainAutoreleasedReturnValue() 方法。如果里面有 objc_retainAutoreleasedReturnValue() 方法丧荐,那么該對象就直接返回給方法或者函數(shù)的調(diào)用方缆瓣。達(dá)到了即使對象不注冊到 autoreleasepool中,也可以返回拿到相應(yīng)的對象虹统。如果沒傳弓坞,那么它就會(huì)走 autorelease 的過程注冊到 autoreleasepool 中。

訪問 __weak 修飾的變量

在訪問 __weak 修飾的變量時(shí)车荔,實(shí)際上必定會(huì)訪問注冊到 AutoreleasePool 的對象渡冻。如下來年兩段代碼是相同的效果:

id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
// 等同于:
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);

為什么會(huì)這樣呢?因?yàn)?__weak 修飾符只持有對象的弱引用夸赫,而在訪問對象的過程中菩帝,該對象有可能被廢棄,如果把被訪問的對象注冊到 AutoreleasePool 中茬腿,就能保證 AutoreleasePool 被銷毀前對象是存在的呼奢。

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 對象在你調(diào)用的方法中被創(chuàng)建握础,然后被放到 AutoreleasePool 中,等到使用結(jié)束后隨著 AutoreleasePool 的銷毀而釋放悴品,所以函數(shù)外 error 對象的使用者不需要關(guān)心它的釋放禀综。

ARC 中,所有這種指針的指針類型(id *)的函數(shù)參數(shù)如果不加修飾符苔严,編譯器會(huì)默認(rèn)將他們認(rèn)定為 __autoreleasing 類型定枷。

有一點(diǎn)特別需要注意的是,某些類的方法會(huì)隱式地使用自己的 AutoreleasePool届氢,在這種時(shí)候使用 __autoreleasing 類型要特別小心欠窒。比如 NSDictionaryenumerateKeysAndObjectsUsingBlock 方法:

- (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í)引用荐虐,保證引用指向的對象不會(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;
        }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末福扬,一起剝皮案震驚了整個(gè)濱河市腕铸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铛碑,老刑警劉巖恬惯,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亚茬,居然都是意外死亡酪耳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門刹缝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碗暗,“玉大人,你說我怎么就攤上這事梢夯⊙粤疲” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵颂砸,是天一觀的道長噪奄。 經(jīng)常有香客問我,道長人乓,這世上最難降的妖魔是什么勤篮? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮色罚,結(jié)果婚禮上碰缔,老公的妹妹穿的比我還像新娘。我一直安慰自己戳护,他們只是感情好金抡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著腌且,像睡著了一般梗肝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铺董,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天巫击,我揣著相機(jī)與錄音,去河邊找鬼。 笑死喘鸟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的驻右。 我是一名探鬼主播什黑,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼堪夭!你這毒婦竟也來了愕把?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤森爽,失蹤者是張志新(化名)和其女友劉穎恨豁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爬迟,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡橘蜜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了付呕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片计福。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖徽职,靈堂內(nèi)的尸體忽然破棺而出象颖,到底是詐尸還是另有隱情,我是刑警寧澤姆钉,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布说订,位于F島的核電站,受9級特大地震影響潮瓶,放射性物質(zhì)發(fā)生泄漏陶冷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一毯辅、第九天 我趴在偏房一處隱蔽的房頂上張望埃叭。 院中可真熱鬧,春花似錦悉罕、人聲如沸赤屋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽类早。三九已至,卻和暖如春嗜逻,著一層夾襖步出監(jiān)牢的瞬間涩僻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逆日,地道東北人嵌巷。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像室抽,于是被迫代替她去往敵國和親搪哪。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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