歷史
蘋果在 2011 年的時候,在 WWDC 大會上提出了自動的引用計數(shù)(ARC)。ARC 背后的原理是依賴編譯器的靜態(tài)分析能力,通過在編譯時找出合理的插入引用計數(shù)管理代碼.
2014 年的 WWDC 大會上,蘋果推出了 Swift 語言,而該語言仍然使用 ARC 技術(shù),作為其內(nèi)存管理方式。
1.引用計數(shù)
1.1什么是引用計數(shù)
引用計數(shù)(Reference Count)是一個簡單而有效的管理對象生命周期的方式。當(dāng)我們創(chuàng)建一個新對象的時候,它的引用計數(shù)為 1,當(dāng)有一個新的指針指向這個對象時,我們將其引用計數(shù)加 1,當(dāng)某個指針不再指向這個對象是,我們將其引用計數(shù)減 1,當(dāng)對象的引用計數(shù)變?yōu)?0 時,說明這個對象不再被任何指針指向了,這個時候我們就可以將對象銷毀,回收內(nèi)存。

2.ARC的內(nèi)存管理
ARC 能夠解決 iOS 開發(fā)中 90% 的內(nèi)存管理問題,但是另外還有 10% 內(nèi)存管理,是需要開發(fā)者自己處理的,這主要就是與底層 Core Foundation 對象交互的那部分,底層的 Core Foundation 對象由于不在 ARC 的管理下,所以需要自己維護(hù)這些對象的引用計數(shù)。
內(nèi)存管理問題
1.循環(huán)引用(block,代理)
2.Core Foundation 對象需要手動管理計數(shù)器
2.1 循環(huán)引用
引用計數(shù)這種管理內(nèi)存的方式雖然使開發(fā)變得簡便,但是也有瑕疵,那就是不能很好解決循環(huán)引用.

上圖所示:對象A和對象B 相互引用成為成員變量,,只有當(dāng)對象A銷毀時,才會對對象A中所以成員變量引用計數(shù)-1.因?yàn)閷ο驛的銷毀依賴于對象B的銷毀,對象B的銷毀依賴于對象A的銷毀.那么就造成了循環(huán)引用。即使這兩個對象在在其他地方已經(jīng)沒有任何指針調(diào)用它們,它們依舊不會釋放.
上述問題不單只是在兩個對象之間出現(xiàn),只有是多個對象中出現(xiàn)了相互持有引用的情況下,都會出現(xiàn)循環(huán)引用
解決方法:
-
主動斷開循環(huán)引用
因?yàn)槭窍嗷ヒ迷斐傻难h(huán)引用,那么只要在 不再需要使用這個對象時,將其主動斷開引用
主動斷開
代碼示例
@property (nonatomic,copy) void(^testBlock)(void);
- (void)viewDidLoad {
[super viewDidLoad];
self.testBlock = ^{
//當(dāng)你在快中主動調(diào)用self時 Xcode會提示你 出現(xiàn)循環(huán)引用 這里只是為了測試
NSLog(@"%@",self);
};
self.testBlock();
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
self.testBlock = nil;
NSLog(@"viewWillDisappear");
}
- (void)dealloc {
NSLog(@"TestViewController -- dealloc");
}
上述代碼中 在 viewDidLoad 中 self引用 testBlock 作為成員變量 , testBlock 值中調(diào)用self, 那么就造成了循環(huán)引用. 所以我們假設(shè)在 viewWillDisappear 中不再需要使用 testBlock 所以將值設(shè)為nil, 那么testBlock 中就不再調(diào)用self了, 所以最后self走了dealloc方法

主動斷開循環(huán)引用這種操作依賴開發(fā)中手動操作,這樣感覺像回到了 MRC年代 誰創(chuàng)建誰釋放 的年代 ,依賴于開發(fā)者自己知道什么時候不再需要,主動斷開. 所以這種方法不常用.
-
使用弱引用
弱引用雖然持有對象,但是并不增加引用計數(shù),這樣就避免了循環(huán)引用的產(chǎn)生。在 iOS 開發(fā)中,弱引用通常在 delegate 模式中使用。
弱引用
聲明屬性時將其修飾為弱引用
@property (nonatomic,weak) id<TestDelegate> delegate;
如果block中調(diào)用到相互持有的對象 那么將其弱引用 就不會造成循環(huán)引用
__weak typeof(self) weakSelf = self;
self.testBlock = ^{
NSLog(@"%@",weakSelf);
};
self.testBlock();
弱引用的實(shí)現(xiàn)原理
弱引用的實(shí)現(xiàn)原理是這樣,系統(tǒng)對于每一個有弱引用的對象,都維護(hù)一個表來記錄它所有的弱引用的指針地址。這樣,當(dāng)一個對象的引用計數(shù)為 0 時,系統(tǒng)就通過這張表,找到所有的弱引用指針,繼而把它們都置成 nil。
從這個原理中,我們可以看出,弱引用的使用是有額外的開銷的。雖然這個開銷很小,但是如果一個地方我們肯定它不需要弱引用的特性,就不應(yīng)該盲目使用弱引用。舉個例子,有人喜歡在手寫界面的時候,將所有界面元素都設(shè)置成 weak 的,這某種程度上與 Xcode 通過 Storyboard 拖拽生成的新變量是一致的。但是我個人認(rèn)為這樣做并不太合適。因?yàn)?
- 我們在創(chuàng)建這個對象時,需要注意臨時使用一個強(qiáng)引用持有它,否則因?yàn)?weak 變量并不持有對象,就會造成一個對象剛被創(chuàng)建就銷毀掉。
- 大部分 ViewController 的視圖對象的生命周期與 ViewController 本身是一致的,沒有必要額外做這個事情。
- 早先蘋果這么設(shè)計,是有歷史原因的。在早年,當(dāng)時系統(tǒng)收到 Memory Warning 的時候,ViewController 的 View 會被 unLoad 掉。這個時候,使用 weak 的視圖變量是有用的,可以保持這些內(nèi)存被回收。但是這個設(shè)計已經(jīng)被廢棄了,替代方案是將相關(guān)視圖的 CALayer 對應(yīng)的 CABackingStore 類型的內(nèi)存區(qū)會被標(biāo)記成 volatile 類型,詳見《再見,viewDidUnload方法》。
2.2 Core Foundation 對象需要手動管理計數(shù)器
一般來說,以CF開頭的系統(tǒng)API都是 CoreFoundation框架的
手動管理,說白了,就是誰創(chuàng)建,誰銷毀
創(chuàng)建兩個CF對象
// 創(chuàng)建一個 CFStringRef 對象
CFStringRef strRef= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8);
// 創(chuàng)建一個 CTFontRef 對象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
創(chuàng)建完后 兩個對象引用計數(shù)器 = 1
當(dāng)這兩個對象不需要再使用時,那么就需要調(diào)用 CFRelease 來使計數(shù)器-1
CFRelease(strRef);
CFRelease(fontRef);

所以對于底層 Core Foundation 對象,我們只需要延續(xù)以前手工管理引用計數(shù)的辦法即可。
其實(shí)Core 開頭的框架 很多有會 retain 和 release 方法, 所以我們一般在使用結(jié)束后,將創(chuàng)建出來的core對象release一次.
除此之外,還有另外一個問題需要解決。在 ARC 下,我們有時需要將一個 Core Foundation 對象轉(zhuǎn)換成一個 Objective-C 對象,這個時候我們需要告訴編譯器,轉(zhuǎn)換過程中的引用計數(shù)需要做如何的調(diào)整。這就引入了bridge相關(guān)的關(guān)鍵字,以下是這些關(guān)鍵字的說明:
- __bridge: 只做類型轉(zhuǎn)換,不修改相關(guān)對象的引用計數(shù),原來的 Core Foundation 對象在不用-時,需要調(diào)用 CFRelease 方法。
- __bridge_retained:類型轉(zhuǎn)換后,將相關(guān)對象的引用計數(shù)加 1,原來的 Core Foundation 對象在不用時,需要調(diào)用 CFRelease 方法。
- __bridge_transfer:類型轉(zhuǎn)換后,將該對象的引用計數(shù)交給 ARC 管理,Core Foundation 對象在不用時,不再需要調(diào)用 CFRelease 方法。
我們根據(jù)具體的業(yè)務(wù)邏輯,合理使用上面的 3 種轉(zhuǎn)換關(guān)鍵字,就可以解決 Core Foundation 對象與 Objective-C 對象相對轉(zhuǎn)換的問題了。
使用 Xcode 檢測循環(huán)引用
在Xcode的的菜單欄中選擇 :Product -> Profile,然后選擇 “Leaks”,再點(diǎn)擊右下角的”Choose” 按鈕開始檢測。如下圖

這個時候 iOS 模擬器會運(yùn)行起來,我們在模擬器里進(jìn)行一些界面的切換操作。稍等幾秒鐘,就可以看到 Instruments 檢測到了我們的這次循環(huán)引用。Instruments 中會用一條紅色的條來表示一次內(nèi)存泄漏的產(chǎn)生。


如果出現(xiàn)下圖的情況 那么說明Profile中的Build Configuration 選的不是Debug


然后重啟Xcode,重新 Product -> Profile 就可以了,還不行就重啟電腦.
總結(jié)
在ARC的環(huán)境下,iOS開發(fā)在內(nèi)存管理方面的工作大部分都不用手動來實(shí)現(xiàn)了,但是我認(rèn)為,我們還是需要去理解引用計數(shù)這種內(nèi)存管理方式,注意循環(huán)引用的問題.
學(xué)會使用 Instruments 工具來調(diào)試項(xiàng)目
最后,愿大家共同進(jìn)步.

