內存泄漏(Memory Leaks)是當一個對象或變量在使用完成後沒有釋放掉,這個對象一直佔有着這塊內存,直到應用中止。若是這種對象過多內存就會耗盡,其它的應用就沒法運行。這個問題在C++、C和Objective-C的MRR中是比較廣泛的問題。javascript
在Objective-C中釋放對象的內存是發送release和autorelease消息,它們都是能夠將引用計數減1,當爲引用計數爲0時候,release消息會使對象馬上釋放,autorelease消息會使對象放入內存釋放池中延遲釋放。java
上代碼:ios
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- NSBundle *bundle = [NSBundle mainBundle];
- NSString *plistPath = [bundle pathForResource:@"team"
- ofType:@"plist"];
- //獲取屬性列表文件中的所有數據
- self.listTeams = [[NSArray alloc] initWithContentsOfFile:plistPath];
- }
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @」CellIdentifier」;
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
- }
- NSUInteger row = [indexPath row];
- NSDictionary *rowDict = [self.listTeams objectAtIndex:row];
- cell.textLabel.text = [rowDict objectForKey:@"name"];
- NSString *imagePath = [rowDict objectForKey:@"image"];
- imagePath = [imagePath stringByAppendingString:@".png"];
- cell.imageView.image = [UIImage imageNamed:imagePath];
- cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
- return cell;
- }
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- NSUInteger row = [indexPath row];
- NSDictionary *rowDict = [self.listTeams objectAtIndex:row];
- NSString *rowValue = [rowDict objectForKey:@"name"];
- NSString *message = [[NSString alloc] initWithFormat:@」您選擇了%@隊。」, rowValue];
- UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@」請選擇球隊」
- message:message
- delegate:self
- cancelButtonTitle:@」Ok」
- otherButtonTitles:nil];
- [alert show];
- [tableView deselectRowAtIndexPath:indexPath animated:YES];
- }
大 家看看上面的3個方法會有什麼問題呢?若是代碼是基於ARC的是沒有問題的,遺憾的是基於MRR,上面的代碼都存在內存泄漏的可能性。理論上講內 存泄漏是對象或變量沒有釋放引發的,但實踐證實並不是全部的未釋放對象或變量都會致使內存泄漏,這與硬件環境和操做系統環境有關,所以咱們須要檢測工具幫助 咱們找到這些「泄漏點」。app
在Xcode中提供了兩種工具幫助查找泄漏點:Analyze和Profile,Analyze是靜態分析工具 能夠經過菜單 Product→Analyze啓動,爲靜態分析以後的代碼畫面;Profile是動態分析工具,這個工具叫「Instruments」,它是Xcode 集成在一塊兒,能夠在Xcode中經過菜單Product→Profile啓動,Instruments有不少Trace Template(跟蹤模板)能夠動態分析和跟蹤內存、CPU和文件系統。框架
咱們能夠兩個工具結合使用查找泄漏點,先使用Analyze靜態分析查找可疑泄漏點,再用Profile動態分析中的Leaks和Allocations跟蹤模板進行動態跟蹤分析,確認這些點是否泄漏,或者是否有新的泄漏出現等。工具
其 中的線段代表了程序執行的路徑,在這個路徑中,1:說明在25行Objective-C對象引用計數是1,說明在這裏建立了一個 Objective-C對象;2:說明在27行引用計數爲1這個,該對象沒有釋放,懷疑有泄漏。這樣的說明已經很明顯的告訴咱們問題所在了, [[NSArray alloc] initWithContentsOfFile:plistPath]建立了一個對象,並賦值給 listTeams屬性所表明的成員變量,然而完成了賦值工做以後,建立的對象並無顯示地發送release和autorelease消息。代碼修改測試
NSArray *array = [[NSArray alloc] initWithContentsOfFile:plistPath];spa
self.listTeams = array;操作系統
[array release];code
咱們看一下tableView:cellForRowAtIndexPath:方法中的疑似泄漏點行末尾的藍色圖標展開分析結果
其 中主要是說明UITableViewCell*類型的cell對象在64行有可能存在泄漏。在表視圖中 tableView:cellForRowAtIndexPath:方法是爲表視圖單元格實例化並設置數據的,所以cell對象實例化後不能立刻 release,應該使用autorelease延遲釋放。能夠在建立cell對象的時候發送autorelease消息,代碼修改以下:
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
咱們看一下tableView:didSelectRowAtIndexPath:方法中的疑似泄漏點有兩個,行末尾的圖標展開分析結果。
message對象建立以後沒有釋放,咱們只須要在[alert show]以後添加[message release]語句代碼就能夠了。在Objective-C中實例化對象有兩種方式:
NSString *message = [[NSString alloc] initWithFormat:@」您選擇了%@隊。」, rowValue]; ①
NSString *message = [NSString stringWithFormat:@"您選擇了%@隊。", rowValue]; ②
① 行所示以init-開頭構造方法,它的是在alloc以後調用該方法咱們稱爲「實例構造方法」,該方法建立對象全部權是調用者,調用者須要對它的 生命週期負責,具體說負責建立和釋放。而另外一種是②行所示string-(去掉NS後類名)開頭方法,它是經過類直接調用咱們稱爲「類級構造方法」,該方 法是建立的對象全部權非調用者全部,調用者不無權釋放它,不然就會因過渡釋放而「殭屍化」,這個問題咱們會在下一節介紹。
UIAlertView*類型alert對象建立以後沒有釋放,咱們只須要在[alert show]以後添加[alert release]語句代碼就能夠了,修改以後的代碼
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- NSUInteger row = [indexPath row];
- NSDictionary *rowDict = [self.listTeams objectAtIndex:row];
- NSString *rowValue = [rowDict objectForKey:@"name"];
- NSString *message = [[NSString alloc] initWithFormat:@」您選擇了%@隊。」, rowValue];
- UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@」請選擇球隊」
- message:message
- delegate:self
- cancelButtonTitle:@」Ok」
- otherButtonTitles:nil];
- [alert show];
- [alert release];
- [message release];
- [tableView deselectRowAtIndexPath:indexPath animated:YES];
- }
上 面介紹的使用Analyze靜態分析查找可疑泄漏點,之因此稱爲「可疑泄漏點」,可是這些點未必必定泄漏,確認這些點是否泄漏還要經過 Profile動態分析工具Instruments中的Leaks和Allocations跟蹤模板,Analyze靜態分析只是一個理論上的預測過程。 經過菜單Product→Profile啓動, Profile動態分析工具中選擇Leaks模板
Instruments 中雖然是選擇了Leaks模板,但默認狀況也會添加Allocations模板,基本上凡是分析內存都會使用 Allocations模板,它能夠監控內存分佈狀況,選中Allocations模板(圖中①區域),右邊③區域會顯示隨着時間的變化內存使用折線圖 表,同時在④區域會顯示內存使用的詳細信息,其中剛剛對象分配狀況。點擊Leaks模板(圖中②區域),能夠查看內存泄漏狀況,若是在③區域有紅線出現, 則有內存泄漏,④區域會顯示泄漏的對象。
出 現的泄漏是在點擊表視圖中單元格測試tableView:didSelectRowAtIndexPath:方法方法時候發生的,其中 NSCFString類型的對象發生了泄漏,NSCFString類型在NSFoundation中是NSString*類型。點擊泄漏對象前面的三角形 展開對象,能夠看到它們的內存地址、佔用字節、所屬框架和響應方法信息。
打開擴展詳細視圖,能夠看到右邊的跟蹤堆棧信息,其中咱們本身應用代碼,能夠點擊進入咱們程序代碼,會打開對應代碼。
代碼77並非泄漏點,而是其中的NSString*類型對象在以後發生了泄漏,所以能夠判定是message對象以後沒有釋放致使泄漏。咱們修改代碼以下:
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- {
- NSUInteger row = [indexPath row];
- NSDictionary *rowDict = [self.listTeams objectAtIndex:row];
- NSString *rowValue = [rowDict objectForKey:@"name"];
- NSString *message = [[NSString alloc] initWithFormat:@」您選擇了%@隊。」, rowValue];
- UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@」請選擇球隊」
- message:message
- delegate:self
- cancelButtonTitle:@」Ok」
- otherButtonTitles:nil];
- [alert show];
- [message release];
- [tableView deselectRowAtIndexPath:indexPath animated:YES];
- }
添 加[message release]語句。不少人還會猜想alert對象(UIAlertView*)會有泄漏,所以從新運行Instruments工具,反覆點擊單元格測 試,並未發現表示內存泄漏的紅線! Instruments工具認爲alert對象不釋放不會引發內存泄漏,若是咱們想進一步評估它對於內存的應用,這個時候咱們能夠看看 Allocations模板的折線圖表,每次點擊總佔用內存數都有所增長,這說明alert對象沒有釋放雖然不是很嚴重,可是也會增長佔用內存,所以 alert對象釋放也是必須的。
這就是咱們介紹的內存泄漏問題解決方法,事實上內存泄漏是極其複雜問題,工具使用是一方面,經驗是另外一方面。提升經驗,而後藉助於工具纔是解決內存泄漏的根本。
轉自:http://guandongsheng.iteye.com/blog/1782699