iOS應(yīng)用性能調(diào)優(yōu)的建議和技巧:初學(xué)者性能提升
本文來(lái)自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序員。這是他的個(gè)人網(wǎng)站:http://www.marcelofabri.com/,你還可以在Twitter上關(guān)注@marcelofabri_。
性能對(duì) iOS 應(yīng)用的開發(fā)尤其重要,如果你的應(yīng)用失去反應(yīng)或者很慢,失望的用戶會(huì)把他們的失望寫滿App Store的評(píng)論。然而由于iOS設(shè)備的限制,有時(shí)搞好性能是一件難事。開發(fā)過(guò)程中你會(huì)有很多需要注意的事項(xiàng),你也很容易在做出選擇時(shí)忘記考慮性能影響。
這正是我寫下這篇文章的原因。這篇文章以一個(gè)方便查看的核對(duì)表的形式整合了你可以用來(lái)提升你app性能的25條建議和技巧。
請(qǐng)耐心讀完這篇文章,為你未來(lái)的app提個(gè)速!
注意:每在優(yōu)化代碼之前,你都要注意一個(gè)問(wèn)題,不要養(yǎng)成”預(yù)優(yōu)化”代碼的錯(cuò)誤習(xí)慣。時(shí)常使用Instruments去profile你的代碼來(lái)發(fā)現(xiàn)需要提升的方面。Matt Galloway寫過(guò)一篇很棒的如何利用Instruments來(lái)優(yōu)化代碼的文章。
還要注意的是,這里列出的其中一些建議是有代價(jià)的,所建議的方式會(huì)提升app的速度或者使它更加高效,但也可能需要花很多功夫去應(yīng)用或者使代碼變得更加復(fù)雜,所以要仔細(xì)選擇。
目錄
我要給出的建議將分為三個(gè)不同的等級(jí): 入門級(jí)、 中級(jí)和進(jìn)階級(jí):
入門級(jí)(這是些你一定會(huì)經(jīng)常用在你app開發(fā)中的建議)
- 1. 用ARC管理內(nèi)存
- 2. 在正確的地方使用reuseIdentifier
- 3. 盡可能使Views透明
- 4. 避免龐大的XIB
- 5. 不要block主線程
- 6. 在Image Views中調(diào)整圖片大小
- 7. 選擇正確的Collection
- 8. 打開gzip壓縮
中級(jí)(這些是你可能在一些相對(duì)復(fù)雜情況下可能用到的)
- 9. 重用和延遲加載Views
- 10. Cache, Cache, 還是Cache!
- 11. 權(quán)衡渲染方法
- 12. 處理內(nèi)存警告
- 13. 重用大開銷的對(duì)象
- 14. 使用Sprite Sheets
- 15. 避免反復(fù)處理數(shù)據(jù)
- 16. 選擇正確的數(shù)據(jù)格式
- 17. 正確地設(shè)定Background Images
- 18. 減少使用Web特性
- 19. 設(shè)定Shadow Path
- 20. 優(yōu)化你的Table View
- 21. 選擇正確的數(shù)據(jù)存儲(chǔ)選項(xiàng)
進(jìn)階級(jí)(這些建議只應(yīng)該在你確信他們可以解決問(wèn)題和得心應(yīng)手的情況下采用)
- 22. 加速啟動(dòng)時(shí)間
- 23. 使用Autorelease Pool
- 24. 選擇是否緩存圖片
- 25. 盡量避免日期格式轉(zhuǎn)換
無(wú)需贅述,讓我們進(jìn)入正題吧~
初學(xué)者性能提升
這個(gè)部分致力于一些能提高性能的基本改變。但所有層次的開發(fā)者都有可能會(huì)從這個(gè)記錄了一些被忽視的項(xiàng)目的小小的性能備忘錄里獲得一些提升。
1. 用ARC管理內(nèi)存
ARC(Automatic Reference Counting, 自動(dòng)引用計(jì)數(shù))和iOS5一起發(fā)布,它避免了最常見的也就是經(jīng)常是由于我們忘記釋放內(nèi)存所造成的內(nèi)存泄露。它自動(dòng)為你管理retain和release的過(guò)程,所以你就不必去手動(dòng)干預(yù)了。
下面是你會(huì)經(jīng)常用來(lái)去創(chuàng)建一個(gè)View的代碼段:
- UIView *view = [[UIView alloc] init];
- // ...
- [self.view addSubview:view];
- [view release];
忘掉代碼段結(jié)尾的release簡(jiǎn)直像記得吃飯一樣簡(jiǎn)單。而ARC會(huì)自動(dòng)在底層為你做這些工作。
除了幫你避免內(nèi)存泄露,ARC還可以幫你提高性能,它能保證釋放掉不再需要的對(duì)象的內(nèi)存。這都啥年代了,你應(yīng)該在你的所有項(xiàng)目里使用ARC!
這里有一些更多關(guān)于ARC的學(xué)習(xí)資源:
- Apple’s official documentation
- Matthijs Hollemans’s Beginning ARC in iOS Tutorial
- Tony Dahbura’s How To Enable ARC in a Cocos2D 2.X Project
- If you still aren’t convinced of the benefits of ARC, check out this article on eight myths about ARC to really convince you why you should be using it!
ARC當(dāng)然不能為你排除所有內(nèi)存泄露的可能性。由于阻塞, retain 周期, 管理不完善的CoreFoundation object(還有C結(jié)構(gòu))或者就是代碼太爛依然能導(dǎo)致內(nèi)存泄露。
這里有一篇很棒的介紹ARC不能做到以及我們?cè)撛趺醋龅奈恼?http://conradstoll.com/blog/2013/1/19/blocks-operations-and-retain-cycles.html。
2. 在正確的地方使用 reuseIdentifier
一個(gè)開發(fā)中常見的錯(cuò)誤就是沒(méi)有給UITableViewCells, UICollectionViewCells,甚至是UITableViewHeaderFooterViews設(shè)置正確的reuseIdentifier。
為了性能***化,table view用 tableView:cellForRowAtIndexPath: 為rows分配cells的時(shí)候,它的數(shù)據(jù)應(yīng)該重用自UITableViewCell。 一個(gè)table view維持一個(gè)隊(duì)列的數(shù)據(jù)可重用的UITableViewCell對(duì)象。
不使用reuseIdentifier的話,每顯示一行table view就不得不設(shè)置全新的cell。這對(duì)性能的影響可是相當(dāng)大的,尤其會(huì)使app的滾動(dòng)體驗(yàn)大打折扣。
自iOS6起,除了UICollectionView的cells和補(bǔ)充views,你也應(yīng)該在header和footer views中使用reuseIdentifiers。
想要使用reuseIdentifiers的話,在一個(gè)table view中添加一個(gè)新的cell時(shí)在data source object中添加這個(gè)方法:
Objective-C
- static NSString *CellIdentifier = @"Cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
這個(gè)方法把那些已經(jīng)存在的cell從隊(duì)列中排除,或者在必要時(shí)使用先前注冊(cè)的nib或者class創(chuàng)造新的cell。如果沒(méi)有可重用的cell,你也沒(méi)有注冊(cè)一個(gè)class或者nib的話,這個(gè)方法返回nil。
3.盡量把views設(shè)置為透明
如果你有透明的Views你應(yīng)該設(shè)置它們的opaque屬性為YES。
原因是這會(huì)使系統(tǒng)用一個(gè)***的方式渲染這些views。這個(gè)簡(jiǎn)單的屬性在IB或者代碼里都可以設(shè)定。
Apple的文檔對(duì)于為圖片設(shè)置透明屬性的描述是:
(opaque)這個(gè)屬性給渲染系統(tǒng)提供了一個(gè)如何處理這個(gè)view的提示。如果設(shè)為YES, 渲染系統(tǒng)就認(rèn)為這個(gè)view是完全不透明的,這使得渲染系統(tǒng)優(yōu)化一些渲染過(guò)程和提高性能。如果設(shè)置為NO,渲染系統(tǒng)正常地和其它內(nèi)容組成這個(gè)View。默認(rèn)值是YES。
在相對(duì)比較靜止的畫面中,設(shè)置這個(gè)屬性不會(huì)有太大影響。然而當(dāng)這個(gè)view嵌在scroll view里邊,或者是一個(gè)復(fù)雜動(dòng)畫的一部分,不設(shè)置這個(gè)屬性的話會(huì)在很大程度上影響app的性能。
你可以在模擬器中用DebugColor Blended Layers選項(xiàng)來(lái)發(fā)現(xiàn)哪些view沒(méi)有被設(shè)置為opaque。目標(biāo)就是,能設(shè)為opaque的就全設(shè)為opaque!
4. 避免過(guò)于龐大的XIB
iOS5中加入的Storyboards(分鏡)正在快速取代XIB。然而XIB在一些場(chǎng)景中仍然很有用。比如你的app需要適應(yīng)iOS5之前的設(shè)備,或者你有一個(gè)自定義的可重用的view,你就不可避免地要用到他們。
如果你不得不XIB的話,使他們盡量簡(jiǎn)單。嘗試為每個(gè)Controller配置一個(gè)單獨(dú)的XIB,盡可能把一個(gè)View Controller的view層次結(jié)構(gòu)分散到單獨(dú)的XIB中去。
需要注意的是,當(dāng)你加載一個(gè)XIB的時(shí)候所有內(nèi)容都被放在了內(nèi)存里,包括任何圖片。如果有一個(gè)不會(huì)即刻用到的view,你這就是在浪費(fèi)寶貴的內(nèi)存資源了。Storyboards就是另一碼事兒了,storyboard僅在需要時(shí)實(shí)例化一個(gè)view controller.
當(dāng)家在XIB是,所有圖片都被chache,如果你在做OS X開發(fā)的話,聲音文件也是。Apple在相關(guān)文檔中的記述是:
當(dāng)你加載一個(gè)引用了圖片或者聲音 資源的nib時(shí),nib加載代碼會(huì)把圖片和聲音文件寫進(jìn)內(nèi)存。在OS X中,圖片和聲音資源被緩存在named cache中以便將來(lái)用到時(shí)獲取。在iOS中,僅圖片資源會(huì)被存進(jìn)named caches。取決于你所在的平臺(tái),使用NSImage 或UIImage 的imageNamed:
方法來(lái)獲取圖片資源。
很明顯,同樣的事情也發(fā)生在storyboards中,但我并沒(méi)有找到任何支持這個(gè)結(jié)論的文檔。如果你了解這個(gè)操作,寫信給我!
想要了解更多關(guān)于storyboards的內(nèi)容的話你可以看看 Matthijs Hollemans的Beginning Storyboards in iOS 5 Part 1 和 Part 2
5. 不要阻塞主線程
永遠(yuǎn)不要使主線程承擔(dān)過(guò)多。因?yàn)閁IKit在主線程上做所有工作,渲染,管理觸摸反應(yīng),回應(yīng)輸入等都需要在它上面完成。
一直使用主線程的風(fēng)險(xiǎn)就是如果你的代碼真的block了主線程,你的app會(huì)失去反應(yīng)。這。。。正是在App Store中拿到一顆星的捷徑 :]
大部分阻礙主進(jìn)程的情形是你的app在做一些牽涉到讀寫外部資源的I/O操作,比如存儲(chǔ)或者網(wǎng)絡(luò)。
你可以使用NSURLConnection
異步地做網(wǎng)絡(luò)操作:
- + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
或者使用像 AFNetworking這樣的框架來(lái)異步地做這些操作。
如果你需要做其它類型的需要耗費(fèi)巨大資源的操作(比如時(shí)間敏感的計(jì)算或者存儲(chǔ)讀寫)那就用 Grand Central Dispatch,或者 NSOperation 和 NSOperationQueues.
下面代碼是使用GCD的模板
Objective-C
- 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
- });
- });
發(fā)現(xiàn)代碼中有一個(gè)嵌套的dispatch_async
嗎?這是因?yàn)槿魏蜺IKit相關(guān)的代碼需要在主線程上進(jìn)行。
如果你對(duì) NSOperation 或者GCD 的細(xì)節(jié)感興趣的話,看看Ray Wenderlich的 Multithreading and Grand Central Dispatch on iOS for Beginners, 還有 Soheil Azarpour 的 How To Use NSOperations and NSOperationQueues 教程。
6. 在Image Views中調(diào)整圖片大小
如果要在UIImageView
中顯示一個(gè)來(lái)自bundle的圖片,你應(yīng)保證圖片的大小和UIImageView的大小相同。在運(yùn)行中縮放圖片是很耗費(fèi)資源的,特別是UIImageView
嵌套在UIScrollView
中的情況下。
如果圖片是從遠(yuǎn)端服務(wù)加載的你不能控制圖片大小,比如在下載前調(diào)整到合適大小的話,你可以在下載完成后,***是用background thread,縮放一次,然后在UIImageView中使用縮放后的圖片。
7. 選擇正確的Collection
學(xué)會(huì)選擇對(duì)業(yè)務(wù)場(chǎng)景最合適的類或者對(duì)象是寫出能效高的代碼的基礎(chǔ)。當(dāng)處理collections時(shí)這句話尤其正確。
Apple有一個(gè) Collections Programming Topics 的文檔詳盡介紹了可用的classes間的差別和你該在哪些場(chǎng)景中使用它們。這對(duì)于任何使用collections的人來(lái)說(shuō)是一個(gè)必讀的文檔。
呵呵,我就知道你因?yàn)樘L(zhǎng)沒(méi)看…這是一些常見collection的總結(jié):
- Arrays: 有序的一組值。使用index來(lái)lookup很快,使用value lookup很慢, 插入/刪除很慢。
- Dictionaries: 存儲(chǔ)鍵值對(duì)。 用鍵來(lái)查找比較快。
- Sets: 無(wú)序的一組值。用值來(lái)查找很快,插入/刪除很快。
8. 打開gzip壓縮
大量app依賴于遠(yuǎn)端資源和第三方API,你可能會(huì)開發(fā)一個(gè)需要從遠(yuǎn)端下載XML, JSON, HTML或者其它格式的app。
問(wèn)題是我們的目標(biāo)是移動(dòng)設(shè)備,因此你就不能指望網(wǎng)絡(luò)狀況有多好。一個(gè)用戶現(xiàn)在還在edge網(wǎng)絡(luò),下一分鐘可能就切換到了3G。不論什么場(chǎng)景,你肯定不想讓你的用戶等太長(zhǎng)時(shí)間。
減小文檔的一個(gè)方式就是在服務(wù)端和你的app中打開gzip。這對(duì)于文字這種能有更高壓縮率的數(shù)據(jù)來(lái)說(shuō)會(huì)有更顯著的效用。
好消息是,iOS已經(jīng)在NSURLConnection中默認(rèn)支持了gzip壓縮,當(dāng)然AFNetworking這些基于它的框架亦然。像Google App Engine這些云服務(wù)提供者也已經(jīng)支持了壓縮輸出。
如果你不知道如何利用Apache或者IIS(服務(wù)器)來(lái)打開gzip,可以讀下這篇文章。