iOS 開發(fā):徹底理解 iOS 內(nèi)存管理(ARC 篇)

本文是 「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饮亏、releaseautorelease耍贾,通過生成正確的代碼去自動釋放或者保持對象。我們完全不用擔(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 操作乐疆。

這里說一下 strongcopy 的區(qū)別划乖。

@property 參數(shù)會幫我們生成對應(yīng)的 settergetter 方法挤土。不同的修飾符生成的 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

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末士复,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子翩活,更是在濱河造成了極大的恐慌阱洪,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菠镇,死亡現(xiàn)場離奇詭異冗荸,居然都是意外死亡,警方通過查閱死者的電腦和手機辟犀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門俏竞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绸硕,“玉大人堂竟,你說我怎么就攤上這事〔E澹” “怎么了出嘹?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咬崔。 經(jīng)常有香客問我税稼,道長,這世上最難降的妖魔是什么垮斯? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任郎仆,我火速辦了婚禮,結(jié)果婚禮上兜蠕,老公的妹妹穿的比我還像新娘扰肌。我一直安慰自己,他們只是感情好熊杨,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布曙旭。 她就那樣靜靜地躺著,像睡著了一般晶府。 火紅的嫁衣襯著肌膚如雪桂躏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天川陆,我揣著相機與錄音剂习,去河邊找鬼。 笑死,一個胖子當著我的面吹牛进倍,可吹牛的內(nèi)容都是我干的土至。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼猾昆,長吁一口氣:“原來是場噩夢啊……” “哼陶因!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起垂蜗,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤楷扬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贴见,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烘苹,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年片部,在試婚紗的時候發(fā)現(xiàn)自己被綠了镣衡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡档悠,死狀恐怖廊鸥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辖所,我是刑警寧澤惰说,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站缘回,受9級特大地震影響吆视,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜酥宴,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一啦吧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拙寡,春花似錦授滓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至擎宝,卻和暖如春郁妈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绍申。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工噩咪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留顾彰,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓胃碾,卻偏偏與公主長得像涨享,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子仆百,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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