iOS 記憶體管理,ios記憶體管理

來源:互聯網
上載者:User

iOS 記憶體管理,ios記憶體管理

本篇隨筆記錄的是看唐巧寫的「iOS開發進階」書籍的「理解記憶體管理」知識點匯總,這裡分享給大家。

 

Objective-C 和 Swift 語言的記憶體管理方式都是基於引用計數「Reference Counting」的,引用計數是一個簡單而有效管理對象生命週期的方式。引用計數分為自動引用計數「ARC: Automatic Reference Counting」和手動引用計數「MRC: Manual Reference Counting」,現在都是用 ARC 了,但是我們還是很有必要瞭解 MRC。

 

1. 引用計數的原理是什嗎?

當我們建立一個新對象時,他的引用計數為1;

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

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

當對象的引用計數為0時,說明此對象不再被任何指標指向,這時我們就可以將對象銷毀,回收記憶體。

 

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

舉個例子:

建立工程,Xcode 預設開啟的是 ARC,我們這裡針對「AppDelegate.m」檔案使用 MRC,進行以下配置:

選擇目標工程,然後在「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]); // 110     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 什麼是殭屍對象、野指標、null 指標呢?

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

野指標:指向殭屍對象(不可用記憶體)的指標,給野指標發送訊息會報錯(EXC_BAD_ACCESS)。

null 指標:沒有指向任何對象的指標(儲存的是 nil、NULL),給null 指標發送訊息不會報錯;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 對象相對轉換的問題了。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.