25個(gè)增強(qiáng)iOS應(yīng)用程序性能的提示和技巧(初級篇)
在開發(fā)iOS應(yīng)用程序時(shí),讓程序具有良好的性能是非常關(guān)鍵的。這也是用戶所期望的,如果你的程序運(yùn)行遲鈍或緩慢,會招致用戶的差評。然而由于iOS設(shè)備的局限性,有時(shí)候要想獲得良好的性能,是很困難的。在開發(fā)過程中,有許多事項(xiàng)需要記住,并且關(guān)于性能影響很容易就忘記。
本文收集了25個(gè)關(guān)于可以提升程序性能的提示和技巧,把性能優(yōu)化技巧分為3個(gè)不同的等級:初級、中級和高級
初級
在開發(fā)過程中,下面這些初級技巧需要時(shí)刻注意:
1.使用ARC進(jìn)行內(nèi)存管理
2.在適當(dāng)?shù)那闆r下使用reuseIdentifier
3.盡可能將View設(shè)置為不透明(Opaque)
4.避免臃腫的XIBs
5.不要阻塞主線程
6.讓圖片的大小跟UIImageView一樣
7.選擇正確的集合
8.使用GZIP壓縮
1) 使用ARC進(jìn)行內(nèi)存管理
ARC是在iOS 5中發(fā)布的,它解決了最常見的內(nèi)存泄露問題——也是開發(fā)者最容易健忘的。ARC的全稱是“Automatic Reference Counting”——自動引用計(jì)數(shù),它會自動的在代碼中做retain/release工作,開發(fā)者不用再手動處理。
下面是創(chuàng)建一個(gè)View通用的一些代碼塊:
- UIView *view = [[UIView alloc] init];
- // ...
- [self.view addSubview:view];
- [view release];
在上面代碼結(jié)束的地方很容易會忘記調(diào)用release。不過當(dāng)使用ARC時(shí),ARC會在后臺自動的幫你調(diào)用release。
ARC除了能避免內(nèi)存泄露外,還有助于程序性能的提升:當(dāng)程序中的對象不再需要的時(shí)候,ARC會自動銷毀對象。所以,你應(yīng)該在工程中使用ARC。
下面是學(xué)習(xí)ARC的一些資源:
蘋果的官方文檔
Matthijs Hollemans的初級ARC
Tony Dahbura的如何在Cocos2D 2.X工程中使用ARC
如果你仍然不確定ARC帶來的好處,那么看一些這篇文章:8個(gè)關(guān)于ARC的神話——這能夠讓你相信你應(yīng)該在工程中使用ARC!
值得注意的是,ARC并不能避免所有的內(nèi)存泄露。使用ARC之后,工程中可能還會有內(nèi)存泄露,不過引起這些內(nèi)存泄露的主要原因是:block,retain循環(huán),對CoreFoundation對象(通常是C結(jié)構(gòu))管理不善,以及真的是代碼沒寫好。
這里有一篇文章是介紹哪些問題是ARC不能解決的 — 以及如何處理這些問題。
2) 在適當(dāng)?shù)那闆r下使用reuseIdentifier

在適當(dāng)?shù)那闆r使用reuseIdentifier
在iOS程序開發(fā)中一個(gè)普遍性的錯(cuò)誤就是沒有正確的為UITableViewCells、UICollectionViewCells和UITableViewHeaderFooterViews設(shè)置reuseIdentifier。
為了獲得最佳性能,當(dāng)在tableView:cellForRowAtIndexPath:方法中返回cell時(shí),table view的數(shù)據(jù)源一般會重用UITableViewCell對象。table view維護(hù)著UITableViewCell對象的一個(gè)隊(duì)列或者列表,這些數(shù)據(jù)源已經(jīng)被標(biāo)記為重用了。
如果沒有使用reuseIdentifier會發(fā)生什么?如果你在程序中沒有使用reuseIdentifier,table view每次顯示一個(gè)row時(shí),都會配置一個(gè)全新的cell。這其實(shí)是一個(gè)非常消耗資源的操作,并且會影響程序中table view滾動的效率。
自iOS 6以來,你可能還希望header和footer views,以及UICollectionView的cell和supplementary views。
為了使用reuseIdentifiers,在table view請求一個(gè)新的cell時(shí),在數(shù)據(jù)源中調(diào)用下面的方法:
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
如果table view維護(hù)的UITableViewCell隊(duì)列或列表中有可用的cell,則從隊(duì)列從移除一個(gè)已經(jīng)存在的cell,如果沒有的話,就從之前注冊的 nib文件或類中創(chuàng)建一個(gè)新的cell。如果沒有可以重用的cell,并且沒有注冊nib文件或類,tableview的 dequeueReusableCellWithIdentifier:方法會返回一個(gè)nil。
3) 盡可能將View設(shè)置為不透明(Opaque)

盡量將view設(shè)置為Opaque
如果view是不透明的,那么應(yīng)該將其opaque屬性設(shè)置為YES。為什么要這樣做呢?這樣設(shè)置可以讓系統(tǒng)以最優(yōu)的方式來繪制view。opaque屬性可以在Interface Builder或代碼中設(shè)置。
蘋果的官方文檔對opaque屬性有如下解釋:
This property provides a hint to the drawing system as to how it should treat the view. If set to YES, the drawing system treats the view as fully opaque, which allows the drawing system to optimize some drawing operations and improve performance. If set to NO, the drawing system composites the view normally with other content. The default value of this property is YES.
(opaque屬性提示繪制系統(tǒng)如何處理view。如果opaque設(shè)置為YES,繪圖系統(tǒng)會將view看為完全不透明,這樣繪圖系統(tǒng)就可以優(yōu)化一些繪制操作以提升性能。如果設(shè)置為NO,那么繪圖系統(tǒng)結(jié)合其它內(nèi)容來處理view。默認(rèn)情況下,這個(gè)屬性是YES。)
如果屏幕是靜止的,那么這個(gè)opaque屬性的設(shè)置與否不是一個(gè)大問題。但是,如果view是嵌入到scroll view中的,或者是復(fù)雜動畫的一部分,不將設(shè)置這個(gè)屬性的話肯定會影響程序的性能!可以通過模擬器的Debug\Color Blended Layers選項(xiàng)來查看哪些view沒有設(shè)置為不透明。為了程序的性能,盡可能的將view設(shè)置為不透明!
4) 避免臃腫的XIBs

避免臃腫的XIB
在iOS 5中開始使用Storyboards,并且將替代XIBs。不過在有些情況下XIBs仍然有用。如果你的程序需要運(yùn)行在裝有iOS 5之前版本的設(shè)備上,或者要自定義可重用的view,那么是避免不了要使用XIBs的。
如果必須要使用XIBs的話,盡量讓XIBs文件簡單。并且每個(gè)view controller對于一個(gè)XIB文件,如果可以的話,把一個(gè)view controller的view不同的層次單獨(dú)分到一個(gè)XIBs文件中。
(注意:當(dāng)把一個(gè)XIB文件加載到內(nèi)存時(shí),XIB文件中的所有內(nèi)容都將被加載到內(nèi)存中,包括圖片。如果有一個(gè)view還不立即使用的話,就會造成內(nèi) 存的浪費(fèi)。而這在storyboard中是不會發(fā)生的,因?yàn)閟toryboard還在需要的時(shí)候才實(shí)例化一個(gè)view controller。)
當(dāng)加載XIB時(shí),所有涉及到的圖片都將被緩存,并且如果是開發(fā)的程序是針對OS X的話,聲音文件也會被加載。蘋果的官方文檔這樣說:
When you load a nib file that contains references to image or sound resources, the nib-loading code reads the actual image or sound file into memory and and caches it. In OS X, image and sound resources are stored in named caches so that you can access them later if needed. In iOS, only image resources are stored in named caches. To access images, you use the imageNamed: method of NSImage or UIImage, depending on your platform.
(當(dāng)加載一個(gè)nib文件時(shí),也會將nib文件涉及到的圖片或聲音資源加載到內(nèi)存中,nib-loading代碼會將實(shí)際的圖片或聲音文件讀取到內(nèi)存 中,并一直緩存著。在OS X中,圖片和聲音資源都存儲在命名緩存中,這樣之后如果需要的話,可以對其進(jìn)行訪問。在iOS中,只有圖片資源被存儲到命名緩存中。要訪問圖片的話,使用 NSImage或UIImage(根據(jù)不同的系統(tǒng))的imageNamed:方法即可。)
顯然,在使用storyboard時(shí)也會發(fā)生類似的緩存操作;不過我沒有找到相關(guān)內(nèi)容的任何資料。想要學(xué)習(xí)storyboard的更多知識嗎?可以看看Matthijs Hollemans寫的iOS 5中:初級Storyboard Part 1和Part2。
別走開,下頁更精彩
#p#
5) 不要阻塞主線程

永遠(yuǎn)都不要在主線程做繁重的任務(wù)。因?yàn)閁IKit的左右任務(wù)都在主線程中進(jìn)行,例如繪制、觸摸管理和輸入響應(yīng)。
在主線程做所有任務(wù)的風(fēng)險(xiǎn)是:如果你的代碼阻塞了主線程,那么程序?qū)⒊霈F(xiàn)反應(yīng)遲鈍。這回招致用戶在App Store上對程序的差評!
在執(zhí)行I/O操作中,大多數(shù)情況下都會祖塞主線程,這些操作需要從讀寫外部資源,例如磁盤或者網(wǎng)絡(luò)。
關(guān)于網(wǎng)絡(luò)操作可以使用NSURLConnection的如下方法,以異步的方式來執(zhí)行:
- + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
或者使用第三方框架,例如AFNetworking。
如果你需要做一些其它類型開銷很大的操作(例如執(zhí)行一個(gè)時(shí)間密集型的計(jì)算或者對磁盤進(jìn)行讀寫),那么就使用GCD(Grand Central Dispatch),或NSOperations 和 NSOperationQueues。
下面的代碼是使用GCD的一個(gè)模板:
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- // switch to a background thread and perform your expensive operation
- dispatch_async(dispatch_get_main_queue(), ^{
- // switch back to the main thread to update your UI
- });
- });
如上代碼,為什么在第一個(gè)dispatch_async里面還嵌套了一個(gè)dispatch_async呢?這是因?yàn)殛P(guān)于UIKit相關(guān)的代碼需要在主線程里面執(zhí)行。
可以看看Ray Wenderlich中的教程:iOS中多線程和GCD—初級,以及Soheil Azarpour的如何使用NSOperations和NSOperationQueues教程。
6) 讓圖片的大小跟UIImageView一樣

確保圖片和UIImageView大小一致
如果需要將程序bundle中的圖片顯示到UIImageView中,請確保圖片和UIImageView的大小是一樣的。因?yàn)閳D片的縮放非常耗費(fèi)資源,特別是將UIImageView嵌入到UIScrollView中。
如果是從遠(yuǎn)程服務(wù)中下載圖片,有時(shí)候你控制不了圖片的尺寸,或者在下載之前無法在服務(wù)器上進(jìn)行圖片的縮放。這種情況,當(dāng)圖片下載完之后,你可以手動進(jìn)行圖片的縮放——做好是在后臺線程中!——然后再在UIImageView中使用縮放過的圖片。
7) 選擇正確的集合

選擇正確的集合
學(xué)習(xí)使用最適合的類或?qū)ο笫蔷帉懜咝Тa的基礎(chǔ)。特別是在處理集合數(shù)據(jù)時(shí),尤為重要。
蘋果的官網(wǎng)上有一篇文章:集合編程主題(Collections Programming Topics)——詳細(xì)的介紹了在集合數(shù)據(jù)中可以使用的類,以及什么情況下使用哪個(gè)類。在使用集合時(shí),每個(gè)開發(fā)者都應(yīng)該閱讀一下這個(gè)文檔。
太長,不想閱讀(TLDR)?下面是常見集合類型的一個(gè)簡介:
•數(shù)組:是一個(gè)值按順序排列的一個(gè)列表。根據(jù)索引可以快速查找,不過根據(jù)值進(jìn)行查找就比較慢,另外插入和刪除也比較慢。
•字典: 存儲鍵/值對。根據(jù)鍵可以快速查找。
•Sets: 是一個(gè)值無序排列的列表,根據(jù)值可以快速查找,另外插入和刪除也比較快。
8) 使用GZIP壓縮

使用GZIP壓縮
越來越多的程序依賴于外部數(shù)據(jù),這些數(shù)據(jù)一般來自遠(yuǎn)程服務(wù)器或者其它的外部APIs。有時(shí)候你需要開發(fā)一個(gè)程序來下載一些數(shù)據(jù),這些數(shù)據(jù)可以是XML,JSON,HTML或者其它一些文本格式。
問題是在移動設(shè)備上的網(wǎng)絡(luò)是不確定的。用戶的設(shè)備可能在EDGE網(wǎng)絡(luò)一分鐘,然后接著又在3G網(wǎng)絡(luò)中。不管在什么情況下,都不要讓用戶等待。
有一個(gè)可以優(yōu)化的選擇:使用GZIP對網(wǎng)絡(luò)傳輸中的數(shù)據(jù)進(jìn)行壓縮,這樣可以減小文件的大小,并加快下載的速度。壓縮對于文本數(shù)據(jù)特別有用,因?yàn)槲谋揪哂泻芨叩膲嚎s比。
iOS中,如果使用NSURLConnection,那么默認(rèn)情況下已經(jīng)支持GZIP壓縮了,并且基于NSURLConnection的框架頁支持GZIP壓縮,如AFNetworking。甚至有些云服務(wù)提供商已經(jīng)提供發(fā)送經(jīng)壓縮過的響應(yīng)內(nèi)容,例如 Google App Engine。
這里有一篇關(guān)于GZIP壓縮很好的文章,介紹了如何在Apache活I(lǐng)IS服務(wù)器中開啟支持GZIP壓縮。