相對電腦而言,移動設(shè)備具有內(nèi)存少、CPU速度慢等特點,因此iOS開發(fā)人員需要盡可能優(yōu)化應(yīng)用的性能。性能優(yōu)化需要考慮的問題很多,下面南昌網(wǎng)站制作公司小編和大家一起來學(xué)習(xí)幾個重要的優(yōu)化方法。今天我們來重點學(xué)習(xí)一下程序性能優(yōu)化之內(nèi)存優(yōu)化。
在Swift語言中,內(nèi)存管理采用ARC(Automatic Reference Counting,自動引用計數(shù))。ARC是與MRC(ManualReference Counting,手動引用計數(shù))相對而言的,這些概念來源自于Objective-C的內(nèi)存管理方式。
1.內(nèi)存管理
這里我們有必要先介紹一下Objective-C的內(nèi)存管理方法,共有3種,分別介紹如下。
MRC。就是由程序員自己負(fù)責(zé)管理對象生命周期,負(fù)責(zé)對象的創(chuàng)建和銷毀。
ARC。采用與MRC一樣的內(nèi)存引用計數(shù)管理方法,但不同的是,它在編譯時會在合適的位置插入對象內(nèi)存釋放(如release、autorelease和retain等),程序員 不用關(guān)心對象釋放的問題。蘋果推薦在新項目中使用ARC,但在iOS 5之前的系統(tǒng)中不能采用ARC。
GC。在Objective-C 2.0之后,內(nèi)存管理出現(xiàn)了類似于Java和C#的內(nèi)存垃圾收集技術(shù),但是垃圾收集與ARC完全不同,垃圾收集是后臺有一個線程負(fù)責(zé)檢查已經(jīng)不再使用的對象,然后釋放之。由于后臺有一個線程一直運(yùn)行,因此會嚴(yán)重影響性能,這也是Java和C#程序的運(yùn)行速度無法超越C++的主要原因。GC技術(shù)不能應(yīng)用于iOS開發(fā),只能應(yīng)用于Mac OS X開發(fā)。
從上面的介紹可知,iOS采用MRC和ARC這兩種方式,ARC是蘋果推薦的方式,MRC方式相對比較原始,對于程序員的能力要求很高,但是它很靈活、方便,很不容易駕馭好。Swift采用ARC管理內(nèi)存,因此使用起來比較簡單。
2.使用 Analyze 和 Instruments 工具解決內(nèi)存泄漏問題
內(nèi)存泄漏指一個對象或變量在使用完成后沒有釋放掉,這個對象一直占用這部分內(nèi)存,直到應(yīng)用停止。如果這種對象過多,內(nèi)存就會耗盡,其他應(yīng)用就無法運(yùn)行。這個問題在C++C和Objective-C的MRC中是比較普遍的問題。
在Objective-C中,釋放對象的內(nèi)存時,可以發(fā)送release和autorelease消息,它們都可以將引用計數(shù)減1。當(dāng)引用計數(shù)為0時,release消息會使對象立刻釋放,autorelease消息會將對象放入內(nèi)存釋放池中延遲釋放。
下面我們看看本節(jié)配套的Objective-C工程中ViewController的代碼片段:
- (void)viewDidLoad
{
[super viewDidLoad];
NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:@"team"
ofType:@"plist"];
//獲取屬性列表文件中的全部數(shù)據(jù)
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,這是沒有問題的,但遺憾的這是基于MRC的,都存在內(nèi)存泄漏的可能性。從理論上講,內(nèi)存泄漏是由對象或變量沒有釋放引起的,但實踐證明并非所有的未釋放對象或變量都會導(dǎo)致內(nèi)存泄漏,這與硬件環(huán)境和操作系統(tǒng)環(huán)境有關(guān),因此我們需要檢測工具幫助我們找到這些“泄漏點”。
在Xcode中,共提供了兩種工具幫助查找泄漏點:Analyze和Instruments。Analyze是靜態(tài)分析工具??梢酝ㄟ^Product→Analyze菜單項啟動。圖1所示為使用Analyze工具進(jìn)行靜態(tài)分析之后的代碼界面。Instruments是動態(tài)分析工具,它與Xcode集成在一起,可以在Xcode中通過Product→Profile菜單項啟動。如圖2所示,Instruments有很多跟蹤模板可以動態(tài)分析和跟蹤內(nèi)存、CPU和文件系統(tǒng)。
圖1 使用Analyze進(jìn)行靜態(tài)分析之后的代碼界面
圖2 Instruments分析工具
我們可以結(jié)合使用這兩個工具查找泄漏點。先使用Analyze靜態(tài)分析查找可疑泄漏點,再用Instruments動態(tài)分析中的Leaks和Allocations跟蹤模板進(jìn)行動態(tài)跟蹤分析,確認(rèn)這些點是否泄漏,或者是否有新的泄漏出現(xiàn)等。
在圖1所示的Analyze靜態(tài)分析結(jié)果中,凡是有 圖標(biāo)的行都是工具發(fā)現(xiàn)的疑似泄漏點。點擊viewDidLoad方法中疑似泄漏點行末尾的 圖標(biāo),會展開分析結(jié)果,具體如圖3所示。
圖3 viewDidLoad方法的疑似泄漏點展開結(jié)果
圖3中的線表明了程序執(zhí)行的路徑。在這個路徑中,第1處說明在第25行中,Objective-C對象的引用計數(shù)是1,說明在這里創(chuàng)建了一個Objective-C對象。第2處說明在第27行中引用計數(shù)為1,該對象沒有釋放,懷疑有泄漏。這樣的說明已經(jīng)很明顯地告訴我們問題所在了,[[NSArray alloc]initWithContentsOfFile:plistPath]創(chuàng)建了一個對象,并賦值給listTeams屬性所代表的成員變量,然而完成了賦值工作之后,創(chuàng)建的對象并沒有顯式地發(fā)送release和autorelease消息。這里可以將代碼修改如下:
NSArray *array = [[NSArray alloc] initWithContentsOfFile:plistPath];
self.listTeams = array;
[array release];
點擊tableView:cellForRowAtIndexPath:方法中疑似泄漏點行末尾的 圖標(biāo),展開分析結(jié)果,如圖4所示。
圖4 tableView:cellForRowAtIndexPath:方法的疑似泄漏點展開結(jié)果
這主要說明UITableViewCell *類型的cell對象在第64行有可能存在泄漏。在表視圖中tableView:
cellForRowAtIndexPath:方法用于實例化表視圖單元格并設(shè)置數(shù)據(jù),因此cell對象實例化后不能馬上釋放,而應(yīng)該使用autorelease延遲釋放??梢栽趧?chuàng)建cell對象時發(fā)送autorelease消息,將代碼修改如下:
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
}
我們再看一下tableView:didSelectRowAtIndexPath:方法中的疑似泄漏點,共有兩個。點擊行末尾的 圖標(biāo),展開分析結(jié)果,具體如圖5和圖6所示。
圖5 tableView:didSelectRowAtIndexPath:方法疑似泄漏點1的展開結(jié)果
圖6 tableView:didSelectRowAtIndexPath:方法疑似泄漏點2的展開結(jié)果
圖5所示的是message對象創(chuàng)建之后沒有釋放,我們只需要在[alert show]之后添加[message release]語句代碼就可以了。
在Objective-C中,實例化對象有如下兩種方式:
NSString *message = [[NSString alloc] initWithFormat:@"您選擇了%@隊。", rowValue]; ①
NSString *message = [NSString stringWithFormat:@"您選擇了%@隊。", rowValue]; ②
第①行所示的以init開頭的構(gòu)造方法在alloc之后調(diào)用,我們將其稱為“實例構(gòu)造方法”。對于使用該方法創(chuàng)建的對象,其所有權(quán)是調(diào)用者,調(diào)用者需要對它的生命周期負(fù)責(zé),具體說就是負(fù)責(zé)創(chuàng)建和釋放。第②行所示的以string開頭的方法,它通過類直接調(diào)用,我們將其稱為“類級構(gòu)造方法”。
南昌網(wǎng)站制作小編提示:采用alloc、new、copy和mutableCopy所創(chuàng)建的對象,所有權(quán)屬于調(diào)用者,它的生命周期由調(diào)用者管理,調(diào)用者負(fù)責(zé)通過release或autorelease方法釋放對象。
圖6所示的是UIAlertView *類型的alert對象創(chuàng)建后沒有釋放,我們只需要在[alert show]之后添加[alertrelease]語句就可以了。修改之后的代碼如下:
上面介紹的是使用Analyze靜態(tài)分析查找可疑泄漏點。之所以稱為“可疑泄漏點”,是因為這些點未必一定泄漏。確認(rèn)這些點是否泄漏,還要通過Instruments動態(tài)分析工具中的Leaks和Allocations跟蹤模板。Analyze靜態(tài)分析只是一個理論上的預(yù)測過程。在Xcode中通過Product→Profile菜單項啟動Instruments動態(tài)分析工具,接著選擇Leaks模板,打開的界面如圖7所示。
圖7 Instruments的Leaks模板
在Instruments中,雖然選擇了Leaks模板,但默認(rèn)情況下也添加Allocations模板?;旧戏彩欠治鰞?nèi)存都會使用Allocations模板,它可以監(jiān)控內(nèi)存分布情況。選中Allocations模板(圖中①區(qū)域),右邊的③區(qū)域會顯示隨著時間的變化內(nèi)存使用的折線圖,同時在④區(qū)域會顯示內(nèi)存使用的詳細(xì)信息以及對象分配情況。點擊Leaks模板(圖中②區(qū)域),可以查看內(nèi)存泄漏情況。如圖8所示,如果在③區(qū)域有紅線出現(xiàn),則有內(nèi)存泄漏,④區(qū)域則會顯示泄漏的對象。
圖8 Instruments檢測到的內(nèi)存泄漏
圖8中出現(xiàn)的泄漏是在點擊表視圖中單元格測試tableView:didSelectRowAtIndexPath:方法時發(fā)生的,點擊泄漏對象Address列后面的 按鈕,會進(jìn)入如圖9所示的詳細(xì)界面??梢园l(fā)現(xiàn),里面有兩個對象,可以看到它們的內(nèi)存地址、占用字節(jié)、所屬框架和響應(yīng)方法等信息。
圖9 查看泄漏的詳細(xì)信息
在圖9中,點擊右邊的跟蹤堆棧信息按鈕 ,如圖10所示,其中 圖標(biāo)所示的條目是我們自己應(yīng)用的代碼,點擊它即可進(jìn)入程序代碼,如圖11所示。
圖10 查看堆棧信息
圖11 查看泄漏點
圖11所示的第84行代碼是可能的泄漏點。事實上,內(nèi)存泄漏是極其復(fù)雜的問題,工具使用是一方面,經(jīng)驗是另一方面。提高經(jīng)驗,然后借助于工具才是解決內(nèi)存泄漏的根本。
3.查找和解決僵尸對象
內(nèi)存泄漏指一個對象或變量在使用完成后沒有釋放掉。如果我們走了另外一個極端情況,會是什么樣呢?這就導(dǎo)致過度釋放問題,從而使對象“僵尸化”,該對象則被稱為僵尸對象。如果一個對象已經(jīng)被釋放過了,或者調(diào)用者沒有這個對象的所有權(quán)而釋放它,都會造成過度釋放,產(chǎn)生僵尸對象。
對于很多人來說,僵尸對象或許聽起來很恐怖、也很陌生,但是如果說起EXEC_BAD_ACCESS異常,可能大家并不陌生。如果應(yīng)用的某個方法試圖調(diào)用僵尸對象,則會崩潰(應(yīng)用直接跳出),并拋出EXEC_BAD_ACCESS異常。
下面我們看看本節(jié)配套Objective-C工程中ViewController的代碼片段:
注意看上述代碼中的粗體部分,你會發(fā)現(xiàn)什么問題嗎?程序運(yùn)行時,拋出EXEC_BAD_ACCESS異常。假設(shè)我們現(xiàn)在無法找到問題,可以使用Instruments工具的Zombies跟蹤模板。按照圖12所示選擇Zombies模板,接著點擊Profile按鈕就可以進(jìn)入了。
圖12 Instruments的Zombies模板
這樣在程序運(yùn)行時,如果發(fā)現(xiàn)僵尸對象,就會彈出一個對話框,如圖13所示,點擊其中的 按鈕,便會在屏幕下方顯示僵尸對象的詳細(xì)信息(如圖14所示)。
圖13 僵尸對象信息
圖14 僵尸對象的詳細(xì)信息
從圖14可見,僵尸對象為UIAlertView類型,從上到下僵尸對象的引用計數(shù)變化是:1(創(chuàng)建)→ 0(釋放)→ ?1(僵尸化)。點擊View中的 按鈕,打開堆棧跟蹤信息視圖,然后在右邊的跟蹤堆棧信息中點擊 條目進(jìn)入我們的程序代碼并定位到僵尸對象,如圖15所示。
圖15 定位僵尸對象
在圖15中,3條高亮顯示的代碼會影響對象的引用計數(shù),從中我們不難發(fā)現(xiàn)問題。就本例而言,我們需要將本節(jié)開頭第②行代碼[alert show]放在[alert release]語句之前調(diào)用就可以了。
4.autorelease 的使用問題
在MRC中,釋放對象通過release或autorelease消息實現(xiàn),其中release消息會立刻使引用計數(shù)減一,autorelease消息會使對象放入內(nèi)存釋放池中延遲釋放,對象的引用計數(shù)并不變化,而是向內(nèi)存釋放池中添加一條記錄,直到池被銷毀前通知池中的所有對象全部發(fā)送release消息才真正將引用計數(shù)減少。
由于使用autorelease消息會使對象延遲釋放,所以除非必須,否則不要使用它釋放對象。在iOS程序中,內(nèi)存釋放池的釋放默認(rèn)在程序結(jié)束。應(yīng)用程序入口main.m文件的代碼如下:
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
代碼被包裹在@autoreleasepool {…}之間,這是池的作用范圍,默認(rèn)是整個應(yīng)用。如果產(chǎn)生大量對象,采用autorelease釋放也會導(dǎo)致內(nèi)存泄漏。那么什么時候才必須使autorelease呢?我們看看下面的代碼:
- (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] autorelease];
}
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;
}
在上述代碼中,cell對象不能馬上釋放,我們需要使用它設(shè)置表視圖界面。autorelease一般用在為其他調(diào)用者提供對象的方法中,對象在該方法中不能馬上釋放,而需要延遲釋放。
此外,還有一種情況需要使用autorelease,就是使用靜態(tài)工廠方法獲得對象時,因為靜態(tài)工廠方法內(nèi)部使用了autorelease。使用靜態(tài)工廠方法的代碼如下:
NSString *message = [NSString stringWithFormat:@"您選擇了%@隊。", rowValue];
該對象的所有權(quán)雖然不是當(dāng)前調(diào)用者,但它是由iOS系統(tǒng)通過發(fā)送autorelease消息放入到池中的。當(dāng)然,這一切對于開發(fā)者都是不可見的,我們也要注意減少使用這樣的語句。
5.響應(yīng)內(nèi)存警告
好的應(yīng)用應(yīng)該在系統(tǒng)內(nèi)存警告的情況下釋放一些可以重新創(chuàng)建的資源。在iOS中,我們可以在應(yīng)用程序委托對象、視圖控制器以及其他類中獲得系統(tǒng)內(nèi)存警告消息。
(1) 應(yīng)用程序委托對象
在應(yīng)用程序委托對象中接收內(nèi)存警告消息,需要寫applicationDidReceiveMemoryWarning:方法,具體可參考本節(jié)實例代碼中AppDelegate的代碼片段:
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
NSLog(@"AppDelegate中調(diào)用applicationDidReceiveMemoryWarning:");
}
(1) 視圖控制器
在視圖控制器中接收內(nèi)存警告消息,需要重寫didReceiveMemoryWarning方法,具體可參考本節(jié)實例代碼中
ViewController的代碼片段:
- (void)didReceiveMemoryWarning
{
NSLog(@"ViewController中didReceiveMemoryWarning調(diào)用");
[super didReceiveMemoryWarning];
//釋放成員變量
[_listTeams release];
}
注意,釋放資源代碼應(yīng)該放在[super didReceiveMemoryWarning]語句后面。
(1) 其他類
在其他類中可以使用通知。在發(fā)生內(nèi)存警告時,iOS系統(tǒng)會發(fā)出UIApplicationDidReceiveMemoryWarningNotification通知,凡是在通知中心注冊了該通知的類都會接收到內(nèi)存警告通知,具體可參考本節(jié)實例代碼中ViewController的代碼片段:
- (void)viewDidLoad
{
[super viewDidLoad];
NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:@"team"
ofType:@"plist"];
//獲取屬性列表文件中的全部數(shù)據(jù)
NSArray *array = [[NSArray alloc] initWithContentsOfFile:plistPath];
self.listTeams = array;
[array release];
//接收內(nèi)存警告通知,調(diào)用handleMemoryWarning方法處理
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(handleMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
//處理內(nèi)存警告
-(void) handleMemoryWarning
{
NSLog(@"ViewController中handleMemoryWarning調(diào)用");
}
在上述代碼中,我們在viewDidLoad方法中注冊UIApplicationDidReceiveMemoryWarningNotification消息,接收到報警信息后調(diào)用handleMemoryWarning方法。這些代碼完全可以寫在其他類中,直接在ViewController中重寫didReceiveMemoryWarning方法就可以了。本例只是示意性地介紹一下UIApplicationDidReceiveMemoryWarningNotification報警消息。
內(nèi)存警告在設(shè)備上并不經(jīng)常出現(xiàn),一般我們沒有辦法模擬,但模擬器上有一個功能可以模擬內(nèi)存警告。啟動模擬器,選擇“硬件”→“模擬內(nèi)存警告”模擬器菜單,這時我們會在輸出窗口中看到內(nèi)存警告發(fā)生了,具體如下所示:
2014-11-19 15:58:51.032 MemoryLeakSample[1396:41574] Received memory warning.
2014-11-19 15:58:51.033 MemoryLeakSample[1396:41574] AppDelegate中調(diào)用applicationDidReceiveMemoryWarning:
2014-11-19 15:58:51.034 MemoryLeakSample[1396:41574] ViewController中handleMemoryWarning調(diào)用
2014-11-19 15:58:51.034 MemoryLeakSample[1396:41574] ViewController中didReceiveMemoryWarning調(diào)用
了解更多相關(guān)資訊,關(guān)注南昌網(wǎng)站制作公司--百恒網(wǎng)絡(luò)官方網(wǎng)站。百恒網(wǎng)絡(luò)是一家專業(yè)從事南昌網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、網(wǎng)絡(luò)營銷等服務(wù)的南昌網(wǎng)絡(luò)公司,技術(shù)過硬,經(jīng)驗豐富。如有任何網(wǎng)站方面的問題,百恒網(wǎng)絡(luò)隨時歡迎大家來電咨詢,我們專業(yè)為您解答!