該系列文章系個人讀書筆記及總結性内容,任何組織和個人不得轉載進行商業活動!
上一節介紹了消息傳遞和消息轉發,接下來我們看看記憶體管理相關的内容;
第4章 記憶體管理
恰當的記憶體管理是正确而高效地開發程式的關鍵;
本章詳細介紹為OC程式配置設定和釋放記憶體的途徑、OC的記憶體模型,以及如何編寫實作恰當記憶體管理的程式;
計算機作業系統為程式配置設定有限記憶體,程式應該僅适用其必需的記憶體;
OC語言及其運作時環境提供了支援應用記憶體管理的機制;
4.1 程式的記憶體使用情況
計算機記憶體中存儲和使用程式的方式:
OC可執行程式是由(可執行)代碼、初始化和未初始化的程式資料、連結資訊、重定位資訊、局部資料和動态資料構成的;
其中:
程式資料包括靜态方式聲明的變量和程式常量(即在程式編譯時在代碼中設定的常數);
可執行代碼、程式資料以及連結與重定位資訊會以靜态方式被配置設定記憶體,并在程式的聲明周期中一直存在;
局部(自動)資料在語句中聲明并且隻在該語句中有效,語句執行後,局部資料不會繼續存在;
從文法上來說,OC的複合語句塊就是又括号{}封裝的語句集合;
自動資料被存儲在程式棧中,程式棧通常是在執行程式/線程前就被設定尺寸的記憶體段;
棧用于存儲局部變量和調用方法/函數的上下文資料;
上下文資料包括方法的輸入參數、傳回值,以及調用完方法後繼續執行程式的代碼位址;
作業系統會自動管理這些記憶體;這些資料會獲得棧中的記憶體,而配置設定給這些資料的記憶體會在他們失效後被釋放;
在運作時,OC會将建立的對象(通過NSObject的alloc方法建立的)存儲在動态配置設定的記憶體即堆記憶體中;
以動态方式建立對象就意味着需要進行記憶體管理,因為在堆記憶體中建立的對象永遠不會超過其作用範圍;
地位位址----------------------->高位位址
可執行的二進制代碼->程式資料->堆->未使用的記憶體->棧->輸入程式的資料
為程式代碼配置設定的記憶體是在編寫程式的時候設定的,是以占用的是系統記憶體;
OC程式記憶體管理的必要性:
一方面,程式的棧尺寸(通常)是在程式啟動時确定的,會自動由系統管理;
另一方面,在OC中對象是在程式執行時動态建立的,不會有系統自動回收;
不進行記憶體管理和錯誤的記憶體管理會導緻:
1)記憶體洩漏:
程式沒有釋放不再使用的對象,就會導緻該問題;繼續配置設定記憶體的話,最終會耗盡系統記憶體;
2)懸挂指針:
程式釋放了仍在使用的對象,會導緻該問題;如果将來程式嘗試通路這些對象,就會出現程式錯誤;
4.2 OC的記憶體模型
OC的記憶體管理是通過引用計數實作的;
引用計數是一種通過對象的唯一引用,确定對象是否正在被使用的技術;
如果對象的引用計數降到了0,對象就會被視為不再有用,而且運作時系統也會釋放它的記憶體;
蘋果OC開發環境提供了兩種記憶體管理機制:
手動管理(MRR)(也就是我們通常說的MRC,C是count的意思,是相對ARC來說的);
自動引用計數(ARC);
4.3 手動管理
手動管理(MRR):是一種建立在對象所有權概念上的記憶體管理機制;
隻要對象所有者還存在,對象就不會被OC運作時環境釋放;
可以通過編寫代碼精确的管理對象的回收利用;
我們先來了解下通路和使用對象的方式,以及通路對象與對象所有權之間的差别;
4.3.1 對象引用和對象所有權
OC對象是通過指向OC對象記憶體位址的變量,以間接方式通路的;
這種變量就是C語言中的指針;
指針的應用範圍很廣,包括OC的基本資料類型與C語言的資料類型;
但,對象指針專門用于OC對象的互動操作;
對象指針實作了OC對象的通路功能;
但是,它們本身并不能管理所有權;
比如:
A * a = [[A alloc] init];
A * b = a;
聲明一個對象指針 ,指向另一個同類型的對象指針;我們雖然改變了指針的指向,但是并未設定原始對象的所有權;
這會導緻,如果a被釋放了,b就指向了一個不合法的對象;
要以MRR方式管理對象生命周期:即保留和釋放,在編寫代碼時需要遵守一系列記憶體管理規則;
4.3.2 記憶體管理基本原則
要正确使用MRR,編寫代碼時必須在擷取對象所有權與釋放對象所有權之間進行平衡;
是以需遵循:
1)為建立的所有對象設定所有權:
應使用名稱以alloc、new、copy或mutableCopy開頭的方法建立OC對象;
還應通過向塊發送copy消息,以動态的方式建立OC塊對象;
2)應使用retain方法擷取對象(你尚未擁有)的所有權:
使用NSObject的retain方法可以獲得對象的所有權;
使用retain可以擷取你想要長時間使用的對象的所有權,這樣做通常可以将其存儲為屬性值,并防止其他操作無意中釋放該對象;
3)當不再使用某個對象時,必須放棄其所有權:
使用NSObject類的release和autorelease方法可以釋放對象的所有權;
使用autorelease方法可以在目前自動釋放代碼塊的末尾,放棄對象的所有權;
如果對象的引用計數為0,這兩種方法都會對對象執行dealloc方法;
4)不能放棄不歸你所有的對象的所有權:
這樣做會導緻過早滴釋放這些對象,如果程式嘗試方位已經被釋放的對象,就會出錯;
注意,所有權是對象指針變量相對于對象來講的,對象一直都在,擷取和釋放的是對象指針變量對 對象的所有權;對象的所有權(用計數表示)清0時,相應的記憶體空間也會被運作時系統釋放;
釋放操作:
1)釋放記憶體:
當對象的引用計數為0時,運作時系統會通過NSObject類的dealloc方法釋放掉該對象使用的記憶體;(注意了解這句話)
該方法還提供了放棄子類對象所有權的架構:
-(void)dealloc{
[... release];
...
[super dealloc];
}
你編寫的類(通常都是NSObject類的子類)都應該重寫dealloc方法,調用它們執行個體變量的release方法,然後調用它們父類的dealloc方法;
通過這種方式可以是你編寫的類,遵循類的層次結構以适當的方式放棄對象的所有權;
2)通過autorelease方法延遲釋放操作:
通過NSObject類的autorelease方法可以在自動釋放池代碼塊的末尾,調用對象中的方法;
自動釋放代碼塊 提供了在将來某個時間放棄對象所有權的機制,因而無須編寫調用對象中release方法的具體代碼,并能避免對象立刻被釋放的情況;
使用@autorelease指令可以定義自動釋放池代碼塊:
@autorelease{
//建立自動釋放對象的代碼
...
}
應該總是将建立自動釋放對象的代碼放在自動釋放池代碼塊中;
否則他們将無法收到release消息,進而導緻記憶體洩漏;
用于建立iOS和Mac OS X應用的蘋果應用架構,尤其是AppKit和UIKit,能夠自動提供自動釋放代碼塊;
需要手動編寫自動釋放代碼塊的情況有以下幾種:
(1)你編寫的程式不是以蘋果UI架構為基礎的,如指令行工具;
(2)你實作的邏輯中含有建立很多臨時對象的循環;
為了降低應用占用記憶體的最大值,你在該循環中添加自動釋放池代碼塊,以便在下一次疊代前處置這些對象;
(3)你編寫的應用派生出來一個或多個輔助線程;
你必須在執行輔助線程的位置添加自己編寫的自動釋放池代碼塊,否則你的應用就會記憶體洩漏;
舉個例子:
你可以向這樣寫:
@autorelease{
A * a = [[[A alloc] init] autorelease];
...
}
注意:
上述對象在建立并初始化之後,會立即收到autorelease消息,這通常由一條複合語句實作;
這種設計可以確定所有通過autorelease消息建立的對象都會在程式結束前、在自動釋放代碼塊的末尾被釋放;
4.3.3 使用MRR
示例:
NSString * string1 = [NSString new];//new為建立的對象設定所有權
NSString * string2 = string1; //對象指針變量指派
[string2 retain]; //對象指針變量使用retain擷取對象所有權
AClass * a = [[AClass alloc] init];//alloc為建立的對象設定所有權
[string1 release];
[string2 release]; //對象指針變量使用release放棄對象所有權
[a release];
以上示例展示了 以動态方式建立對象時,調動初始化和釋放方式的順序;
所有對象的建立/保留和釋放消息都達到了平衡;
Xcode的Product菜單的Analyze選項:該工具會分析程式,檢測潛在的記憶體洩漏,懸挂指針等問題;(Commend + shift + B)
當然也可以用Xcode Instrument工具進行記憶體使用情況的分析;
下一節介紹自動引用計數的使用;