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