原文連接地址:http://www.raywenderlich.com/2657/memory-management-in-objective-c-tutorialhtml
著做權聲明:本文由http://www.cnblogs.com/andyque翻譯,歡迎轉載分享。請尊重做者勞動,轉載時保留該聲明和做者博客連接,謝謝!java
教程截圖:node
當我檢查其餘開發人員的代碼時,彷佛最多見的錯誤老是圍繞在以Object-C中的內存管理爲中心。若是您使用的語言是java或C#,它們會自動爲您處理內存管理,但這也會使你對於手工內存管理工做更加迷惑。所以,在本教程中,您將經過一些實踐來學習Object-C中的內存管理是如何工做的。咱們將討論引用計數如何工做,並經過學習內存管理的全部關鍵點來構建一個真實世界的例子——一個關於您喜好的壽司類型的應用程序。程序員
本教程是針對初學者的iOS開發人員或者時關注這個主題的中級開發人員。廢話就少囉嗦了,開始編碼。objective-c
開始數組
在xcode開發環境中,打開File\New Project,選擇iOS\Application\Navigation-based Application,並將新項目命名爲ProMemFun,執行Build\Build and Run, 在模擬器中你會看到一個以下空表視圖:xcode
比方說,咱們但願在這個列表中填入咱們喜好的壽司類型。最簡單的方法是建立一個數組來容下每一種壽司類型的字符串名稱,而後每次咱們顯示一行,從數組中放入合適的字符串到表格中。在rootViewController.h中爲壽司類型聲明一個實例變量,代碼以下:安全
#import <UIKit/UIKit.h>
@interface RootViewController : UITableViewController {
NSArray * _sushiTypes;
}
@end
經過這個聲明,每一個RootViewController實例對象將有空間來存儲一個指向NSArray數組的指針,這是一個Object-C類,使用這個數組初始化後就不能改變它。若是你須要更改一個初始化後的數組(例如,添加一項後),你應該使用NSMutableArray替代。app
也許你會奇怪,爲何咱們在命名的變量前面添加一個下劃線?這剛好是我喜歡作的事情,這樣作有些事情會變得更容易。在後續的關於Objec-C教程中我將討論我爲何喜歡這麼作,可是如今請注意,到目前爲止,咱們所做的是僅僅添加了一個實例變量,沒有作與屬性相關的東東,咱們把它命名爲「如下劃線開頭」,這只是一個我的的喜愛問題,其實它沒有作特別的東西。ide
如今,打開RootViewController.m文件,註釋viewDiaLoad,而後設置如下代碼:
- (void)viewDidLoad {
[super viewDidLoad];
_sushiTypes = [[NSArray alloc] initWithObjects:@"California Roll",
@"Tuna Roll", @"Salmon Roll", @"Unagi Roll",
@"Philadelphia Roll", @"Rainbow Roll",
@"Vegetable Roll", @"Spider Roll",
@"Shrimp Tempura Roll", @"Cucumber Roll",
@"Yellowtail Roll", @"Spicy Tuna Roll",
@"Avocado Roll", @"Scallop Roll",
nil];
}
如今咱們進入內存管理,Object-C中建立的對象使用的是引用計數。這就意味着每個對象都跟蹤有多少其餘的對象引用它。一旦引用計數變爲0,這個對象的內存就會安全的釋放掉。
做爲一個程序員,你要確保對象的引用計數老是準確的。當你在某個地方存儲了一個對象的指針(好比是實例變量),你須要增長引用計數,有時候須要遞減引用計數。
「個人天啊」,你可能會思考,「這聽起來太複雜和混亂了」,不要擔憂,作起來要比聽起來簡單些。
初始化對象和釋放對象的內存
無論何時你在Object-c中建立一個對象,首先你要調用alloc爲這個對象去分配內存空間,而後調用init方法去初始化這個對象,當init方法不帶任何參數時,有時候你會看到程序員用new方法替代(這相似於先調用alloc,而後調用init)。
最重要的是一旦你這麼作了,你會獲得一個新的對象,而且它的引用計數置爲1。所以,當完成全部的工做後,你須要遞減引用計數。
好了,咱們給出一個開頭。仍然是在RootViewController.m中,去文件末尾,像下面同樣設置viewDidUnload和dealloc方法:
- (void)viewDidUnload {
[_sushiTypes release];
_sushiTypes = nil;
}
- (void)dealloc {
[_sushiTypes release];
_sushiTypes = nil;
[super dealloc];
}
記住當你用alloc/init建立一個array時,它的引用計數已經爲1了。所以當你完成與array相關的工做時,須要遞減它的引用計數。在Object-C中,你能夠經過對這個對象調用release方法。
可是你應該在什麼地方release呢?哦,你必定要在dealloc方法中release這個array,顯然易見,當這個viewController銷燬後,你也不會再須要這個array了。因此,記住不管什麼時候你在viewDidLoad中建立一個對象(這個對象的引用計數會初始化爲1),你應該在viewDidUnload中釋放這個對象。不要太擔憂,關於這兒主題我會專門寫一篇教程。
注意,釋放對象後,請將其設置爲nil,若是你試圖調用一個指向nil的指針,你的程序會崩潰。
好了,如今讓咱們使用新的array。首先,替換掉tableView:numberOfRowsInSection 裏面的"return 0",替換成下面的語句:
// Replace "return 0;" in tableView:numberOfRowsInSection with this
return _sushiTypes.count;
這裏意思是說,tableView裏面的數據行數等於sushiTypes數組裏面的記錄個數。
如今,咱們須要告訴table view,每一行具體顯示什麼內容。找到tableView:cellForRowAtIndexPath函數,而後找到註釋 「Configure the cell」,在後面添加下列代碼:
NSString * sushiName = [_sushiTypes objectAtIndex:indexPath.row]; // 1
NSString * sushiString =
[[NSString alloc] initWithFormat:@"%d: %@",
indexPath.row, sushiName]; // 2
cell.textLabel.text = sushiString; // 3
[sushiString release]; // 4
讓咱們一行一行代碼解釋一下上面的程序:
編譯並運行,若是一切OK的話,你將會看到sushi的列表。
目前爲止,你知道了,當你調用alloc/init的時候,引用計數是1,當你用完這個對象的時候,你須要調用release把引用計數變爲0.
接下來,讓咱們討論一下另一種方法----autorelease。
當你給一個對象發送autorelease消息後,它的意思是說「嘿!我想讓你在未來某個時刻被釋放掉,好比當前run loop結束的時候。可是,如今我可以使用你」。
最容易理解的方式就是看代碼。修改 tableView:cellForRowAtIndexPath 方法,找到 「Configure the cell」註釋,在後面添加下列代碼:
NSString * sushiName = [_sushiTypes objectAtIndex:indexPath.row]; // 1
NSString * sushiString =
[[[NSString alloc] initWithFormat:@"%d: %@",
indexPath.row, sushiName] autorelease]; // 2
cell.textLabel.text = sushiString; // 3
所以,和上一次相比,這裏只改了兩個地方。首先,你在第二行結尾的時候調用了autorelease。其次,你把最後一行release的調用代碼移除掉了。
接上來,我解釋一下。在第2行代碼結束的時候,sushiString的引用計數是1,可是,咱們給它發送了一個autorelease消息。這意味着,你能夠在這個函數裏面使用sushiString,可是,一旦下一次run loop被調用的時候,它就會被髮送release對象。而後引用計數改成0,那麼內存也就被釋放掉了。(關於autorelease究竟是怎麼工做的,個人理解是:每個線程都有一個autoreleasePool的棧,裏面放了不少autoreleasePool對象。當你向一個對象發送autorelease消息以後,就會把該對象加到當前棧頂的autoreleasePool中去。噹噹前runLoop結束的時候,就會把這個pool銷燬,同時對它裏面的全部的autorelease對象發送release消息。而autoreleasePool是在當前runLoop開始的時候建立的,並壓入棧頂。那麼什麼是一個runLoop呢?一個UI事件,Timer call, delegate call, 都會是一個新的Runloop。)
在這個例子中,上面的解決辦法很是好,可是,後面咱們不會使用它。然而,若是咱們想要存儲一個變量(可是不retain它),而後在某個地方使用這個變量(好比用戶點擊某一行的時候,選中那一行),那麼咱們就有大麻煩了。由於那樣咱們是在嘗試訪問一個已經銷燬的對象,可想而知,程序確定是crash拉!
有時候,當你調用一些方法的時候,你獲得的返回給你的對象的引用計數是1,可是,它是一個autorelease的對象。你修改一下tableView:cellForRowAtIndexPath方法,修改爲下面的樣子,而後你就知道我剛剛講的是什麼意思了:
NSString * sushiName = [_sushiTypes objectAtIndex:indexPath.row]; // 1
NSString * sushiString =
[NSString stringWithFormat:@"%d: %@",
indexPath.row, sushiName]; // 2
cell.textLabel.text = sushiString; // 3
這裏代碼改變之處是第2行。你不是本身調用 alloc/init/autorelease,而是使用NSString的一個類方法stringWithFormat。這個方法會返回一個引用計數爲1的字符串,而且它是一個autorelease的對象。所以,和上面的寫法同樣,你能夠放心的使用這個字符串,可是,若是你不retain它,而後又在後面某個地方使用它的話,那麼程序就會崩潰。
你可能會奇怪,你怎麼知道哪些對象返回給你的時候是autorelease的?好吧,讓我教你一個簡單的慣用法,具體以下:
Retain Your Wits
若是你如今有一個autorelease對象,而且像在後面繼續使用它,那麼該怎麼辦呢?其實很簡單,你只須要對它發送retain消息就OK了。這樣會把引用計數變爲2,可是,只要出了當前runLoop,那麼引用計數又會變爲1,那麼對象仍是不會銷燬(由於只有引用計數爲0才能銷燬)。
讓咱們來看看具體怎麼作。打開RootViewController.h ,而後在@interface裏面添加一個實例變量:
NSString * _lastSushiSelected;
這裏只是定義了一個新的實例變量,它將用來追蹤選中的最後那一行的字符串。
接下來,修改 tableView:didSelectRowAtIndexPath ,修改以下:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString * sushiName = [_sushiTypes objectAtIndex:indexPath.row]; // 1
NSString * sushiString = [NSString stringWithFormat:@"%d: %@",
indexPath.row, sushiName]; // 2
NSString * message = [NSString stringWithFormat:@"Last sushi: %@. Cur sushi: %@", _lastSushiSelected, sushiString]; // 3
UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:@"Sushi Power!"
message:message
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:@"OK", nil] autorelease]; // 4
[alertView show]; // 5
[_lastSushiSelected release]; // 6
_lastSushiSelected = [sushiString retain]; // 7
}
這裏的代碼比較多,讓咱們一行一行來看:
還有一件事你不能忘記。爲了保存不會有任何內存泄漏,你須要在RootViewController的dealloc方法裏面調用下面方法來釋放內存:
[_lastSushiSelected release];
_lastSushiSelected = nil;
基本上,在dealloc方法被裏面,你須要對「你負責的對象」發送release消息,而且要把它賦值爲nil。
編譯並運行,如今,當你選中一行,你就能夠看到下面的屏幕輸出了。
讓咱們回顧一下所學的知識:
本教程只講述了objc內存管理的很基本的部分,若是想得到更多的信息,請參考蘋果的文檔: Memory Management Programming Guide.
這裏有本教程的完整源代碼。
無論你是一個多麼優秀的開發者,或者你對內存管理的理解有多麼的深刻,你仍是不可避免地要犯一些內存相關的錯誤。所以,在個人下一篇教程中,我將教你們若是使用XCode, Instruments, 和 Zombies來檢測內存泄漏。所以,提早準備好跟我來吧!