iOS 內存管理

本篇隨筆記錄的是看唐巧寫的「iOS開發進階」書籍的「理解內存管理」知識點彙總,這裏分享給你們。編程

 

Objective-C 和 Swift 語言的內存管理方式都是基於引用計數「Reference Counting」的,引用計數是一個簡單而有效管理對象生命週期的方式。引用計數分爲自動引用計數「ARC: Automatic Reference Counting」和手動引用計數「MRC: Manual Reference Counting」,如今都是用 ARC 了,可是咱們仍是頗有必要了解 MRC。服務器

 

1. 引用計數的原理是什麼?架構

當咱們建立一個新對象時,他的引用計數爲1;app

當有一個新的指針指向這個對象時,他的引用計數就加1;工具

當對象關聯的某個指針再也不指向他時,他的引用計數就減1;性能

當對象的引用計數爲0時,說明此對象再也不被任何指針指向,這時咱們就能夠將對象銷燬,回收內存。ui

 

因爲引用計數簡單有效,除了 Objective-C 語言外,Microsoft 的 COM「Component Object Model」、C++11(基於引用計數的智能指針 share_prt)等語言也提供了基於引用計數的內存管理方式。spa

舉個例子:設計

新建工程,Xcode 默認開啓的是 ARC,咱們這裏針對「AppDelegate.m」文件使用 MRC,進行如下配置:3d

選擇目標工程,而後在「Build Phases」的「Compile Sources」下的「AppDelegate.m」文件配置編譯器參數「Compiler Flags」值爲「-fno-objc-arc」

 1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 2     NSObject *objO = [NSObject new];
 3     NSLog(@"Reference Count: %lu", (unsigned long)[objO retainCount]); // 1
 4     NSObject *objB = [objO retain];
 5     NSLog(@"Reference Count: %lu", (unsigned long)[objO retainCount]); // 2
 6     [objO release];
 7     NSLog(@"Reference Count: %lu", (unsigned long)[objO retainCount]); // 1
 8     [objO release];
 9     NSLog(@"Reference Count: %lu", (unsigned long)[objO retainCount]); // 1
10     
11     [objO setValue:nil forKey:@"test"]; // 殭屍對象,向野指針發送消息會報錯(EXC_BAD_ACCESS)
12     
13     return YES;
14 }

Xcode 默認不會監控殭屍對象,這裏咱們配置開啓他,而後就能夠看到具體的跟蹤信息了:

 

也能夠經過選擇「Product」下的「Profile」來打開「Instruments」工具集。而後選擇「Zombies」,再單擊右下角的「Choose」按鈕進入檢測界面,這時點擊左上角的「Record」紅色圓點按鈕開始檢測。

 

1.1 上面例子,爲何最後一次經過 retainCount 獲取的值爲1,而不是爲0呢?

由於該對象的內存已經被回收,咱們向一個被回收的對象發送 retainCount 消息,他的輸出結果是不肯定的,若是該對象所佔內存被複用了,那麼就可能形成程序異常崩潰。

並且當最後一次執行 release 時,系統已經知道立刻要回收內存了,就不必再將 retainCount 減1,由於無論減不減1,該對象都會被回收,回收後他所在內存區域(包括 retainCount 值)就沒有意義了。不將 retainCount 減1變爲0,能夠減小一次內存操做,加快對象的回收。

 

1.2 什麼是殭屍對象、野指針、空指針呢?

殭屍對象:所佔用內存已經被回收的對象,殭屍對象不能再使用。

野指針:指向殭屍對象(不可用內存)的指針,給野指針發送消息會報錯(EXC_BAD_ACCESS)。

空指針:沒有指向任何對象的指針(存儲的是 nil、NULL),給空指針發送消息不會報錯;空指針的一個經典使用場景就是在開發中獲取服務器 API 數據時,轉換野指針爲空指針,避免發送消息報錯。

 

2. 爲何須要引用計數?

從上面簡單例子,咱們還看不出引用計數真正的用處,由於該對象的生命週期只是在一個方法內。在真實的應用場景中,咱們在方法內使用臨時對象,一般不須要修改他的引用計數,只須要在方法返回前銷燬對象就能夠了。

然而,引用計數真正派上用場的場景是在面向對象的程序設計架構中,用於對象之間傳遞和共享數據。

 

舉個例子:

假如對象 A 生成了一個對象 O,須要調用對象 B 的某個方法,將對象 O 做爲參數傳遞過去。

在沒有引用計數的狀況下,通常內存管理的原則是「誰申請誰釋放」,那麼對象 A 就須要在對象 B 再也不須要對象 O 的時候,將對象 O 銷燬。但對象 B 可能臨時用一下對象 O,也能夠以爲他重要,將他設置爲本身的一個成員變量,在這種狀況下,何時銷燬對象 O 就成了一個難題了。

對於以上狀況有兩種作法:

(1)對象 A 在調用完對象 B 的某個方法以後,立刻銷燬參數對象 O,而後對象 B 須要將對象 O 複製一份,生成另外一個對象 O2,同時本身來管理對象 O2 的生命週期。可是這種作法有一個很大的問題,就是他帶來更多的內存申請、複製、釋放的工做。原本能夠複用的對象,由於不方便管理他的生命週期,就簡單地把他銷燬,又從新構造一份同樣的,實在太影響性能。

(2)對象 A 只負責生成對象 O,以後就由對象 B 負責完成對象 O 的銷燬工做。若是對象 B 只是臨時用一下對象 O,就能夠用完後立刻銷燬,若是對象 B 須要長時間使用對象 O,就不銷燬他。這種作法看似解決了對象複製的問題,可是他強烈依賴於 A 和 B 兩個對象的配合,代碼維護者須要明確地記住這種編程約定。並且,因爲對象 O 的生成和釋放在不一樣對象中,使得他的內存管理代碼分散在不一樣對象中,管理起來也很費勁。若是這個時候狀況更加複雜一些,例如對象 B 須要再向對象 C 傳遞參數對象 O,那麼這個對象在對象 C 中又不能讓對象 C 管理。因此這種方法帶來的複雜度更高,更加不可取。

引用計數的出現很好地解決這個問題,在參數對象 O 的傳遞過程當中,哪些對象須要長時間使用他,就把他的引用計數加1,使用完就減1。全部對象遵照這個規則,對象的生命週期管理就能夠徹底交給引用計數了。咱們也能夠很方便地享受到共享對象帶來的好處。

 

2.1 什麼是循環引用「Reference Cycles」問題,怎麼解決呢?

引用計數這種內存管理方式雖然簡單,但有一個瑕疵就是他不能自動解決循環引用的問題。

舉個例子:

對象 A 和對象 B 相互引用對方做爲本身的成員變量,只有當本身銷燬時,纔將本身的成員變量的引用計數減1,由於對象 A 和對象 B 的銷燬相互依賴,這樣就形成咱們所說的循環引用問題了。

循環引用會致使即便外界已經沒有任何指針可以訪問他們了,可是他們所佔資源仍然沒法釋放的狀況。

解決循環引用問題主要有兩種方法:

(1)明確知道哪裏存在循環引用,合理時機主動斷開環中的一個引用,使得對象得以回收。這種方法不經常使用,由於他依賴開發人員本身手工顯式控制,至關於回到之前「誰申請誰釋放」的內存管理年代。

(2)使用弱引用「Weak Reference」,「weak」「__weak」類型,這種方法經常使用。弱引用雖然持有對象,可是並不增長他的引用計數。弱引用的一個經典使用場景就是委託代理「delegate」協議模式。

 

2.2 Xcode 中有什麼工具能夠檢測循環引用嗎?

在 Xcode 中有「Instruments」工具集能夠很方便地檢測循環引用。

舉個例子:

1 - (void)viewDidLoad {
2     [super viewDidLoad];
3     
4     NSMutableArray *mArrFirst = [NSMutableArray array];
5     NSMutableArray *mArrSecond = [NSMutableArray array];
6     [mArrFirst addObject:mArrSecond];
7     [mArrSecond addObject:mArrFirst];
8 }

能夠選擇「Product」下的「Profile」來打開「Instruments」工具集。

而後選擇「Leaks」,再單擊右下角的「Choose」按鈕進入檢測界面,這時點擊左上角的「Record」紅色圓點按鈕開始檢測。

 

 

 

 

3. Core Foundation 對象的內存管理

ARC 是編譯器特性,他不是運行時特性,更不是垃圾回收器「GC」。

ARC 可以解決 iOS 開發中90%的內存管理問題,可是另外10%的內存管理問題是須要開發人員本身處理的,這主要是與底層 Core Foundation 對象交互的部分,底層 Core Foundation 對象因爲不在 ARC 的管理下,因此須要本身維護這些對象的引用計數。

實際上 Core Foundation 對象使用的 CFRetain 和 CFRelease 方法,能夠認爲與 Objective-C 對象的 retain 和 release 方法等價,因此咱們能夠以 MRC 的方式進行相似管理。

 

3.1 在 ARC 中,經過什麼方式能夠把 Core Foundation 對象轉換爲 Objective-C 對象呢?

轉換的過程,實際上是告訴編譯器,對象的引用計數如何調整。

這裏咱們可使用橋接「bridge」相關關鍵字來進行轉換工做,如下是這些(雙下劃線)關鍵字的說明:

(1)__bridge:只作類型轉換,不修改相關對象的引用計數,原來的 Core Foundation 對象在不用時,須要調用 CFRelease 方法。

(2)__bridge_retained:類型轉換後,將相關對象的引用計數加1,原來的 Core Foundation 對象在不用時,須要調用 CFRelease 方法。

(3)__bridge_transfer:類型轉換後,將相關對象的引用計數交給 ARC 管理,原來的 Core Foundation 對象在不用時,不須要調用 CFRelease 方法。

咱們根據具體的業務邏輯,合理使用上面的三種轉換關鍵字,就能夠解決 Core Foundation 對象與 Objective-C 對象相對轉換的問題了。

相關文章
相關標籤/搜索