iOS App的性能關註點 雖然iPhone的機能越來越好,但是app的功能也越來越複雜,性能從來都是移動開發的核心關註點之一。我們說一個app性能好,不是簡單指感覺運行速度快,而應該是指應用啟動快速、UI反饋響應及時、列表滾動操作流暢、記憶體使用合理,當然更不能隨隨便便Crash啦。工程師開發應用時 ...
iOS App的性能關註點
雖然iPhone的機能越來越好,但是app的功能也越來越複雜,性能從來都是移動開發的核心關註點之一。我們說一個app性能好,不是簡單指感覺運行速度快,而應該是指應用啟動快速、UI反饋響應及時、列表滾動操作流暢、記憶體使用合理,當然更不能隨隨便便Crash啦。工程師開發應用時除了在設計上要避免性能“坑”的出現,在實際遇到“坑”時也要能很快定位原因所在。定位性能問題原因當然不能靠猜,合理的方法是使用工具測量評估出投資回報最高的問題點,然後再加以優化。
本文會從以下幾點介紹如何分析和優化iOS app的性能:啟動時間、用戶響應、記憶體、圖形動畫、文件和網路I/O。其中會用到Apple出品的性能分析神器“Instruments”。
啟動時間
應用啟動時間長短對用戶第一次體驗至關重要,同時系統對應用的啟動、恢復等狀態的運行時間也有嚴格的要求,在應用超時的情況下系統會直接關閉應用。以下是幾個常見場景下系統對app運行時間的要求: * Launch 20秒 Resume 10秒 Suspend 10秒 Quit 6秒 Background Task 10分鐘
要獲取準確的app啟動所需時間,最簡單的方法時首先在main.c中添加如下代碼:
1
2
3
|
|
然後在AppDelegate的回調方法application:didFinishLaunchingWithOptions
中添加:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@”Lauched in %f seconds.”, (CFAbsoluteTimeGetCurrent() – StartTime));
});可能你會覺得為什麼這樣可拿到系統啟動的時間,因為這個dispatch_async中提交的工作會在app主線程啟動後的下一個run lopp中運行,此時app已經完成了載入並且將要顯示第一幀畫面,也就是系統會運行到`-[UIApplication _reportAppLaunchFinished]`之前。下圖是用Instruments工具Time Profiler跑的調用棧,Instruments的使用方法建議看WWDC中與performance相關的[session錄像](https://developer.apple.com/videos/wwdc),文字寫起來太單薄不夠直觀哈。
從圖中我們可以看到在系統調用[UIApplication _reportAppLaunchFinished]
之前完成了系統回調application:didFinishLaunchingWithOptions
。
App的啟動會包括以下幾個部分(來自WWDC 2012 Session 235):
1)鏈接和載入:可以在Time Profile中顯示dyld載入庫函數,庫會被映射到地址空間,同時完成綁定以及靜態初始化。
2)UIKit初始化:如果應用的Root View Controller是由XIB實現的,也會在啟動時被初始化。
3)應用回調:調用UIApplicationDeleagte的回調:application:didFinishLaunchingWithOptions
4)第一次Core Animation調用:在啟動後的方法-[UIApplication _resportAppLaunchFinished]
中調用CA::Transaction::commit
實現第一幀畫面的繪製。如果你的程式啟動很慢,能 做的首先是將與顯示第一屏畫面無關的操作放到之後執行;如果是用XIB文件load第一屏,XIB文件中的View層也要如果扁平,不要有太多圖層。
用戶響應
如何能夠讓用戶覺得你的app響應迅速呢?當然是app用戶所觸發的操作都能得到立刻響應,即用戶事件(User Event)能夠被主線程的run loop及時處理。什麼是run loop?可以想象成一個處理事件的select多路復用。主線程中的run loop當然主要是為了處理用戶產生的事件啦,例如點擊、滾動等。以後我們會詳細聊聊run loop這個讓人迷惑的東東。
要讓主線程的run loop更好的響應用戶事件,工程師應該儘量減少主線程乾重活的時間,尤其是讀文件啊,網路操作啊,大量運算啊這類重活,如果是阻塞操作,那就更是大忌了。我們可以用多線程(NSThread、NSOperationQueue, GCD,下一篇Blog就會聊到這多線程)將重活移出主線程,這屬於顯式併發。還有種隱式併發,例如view和layer的動畫、layer的繪製以及PNG圖片的解碼都是在另一個子線程中執行的。除了使用多線程技術減輕主線程的負擔外,減少主線程中阻塞也是提升用戶體驗的一個方法。使用Instruments中Time Profiler工具中的”Recod thread waiting”選項可以統計出app運行時各個線程中的阻塞系統調用情況,例如文件讀寫read/write,網路讀寫send/recv,加鎖psynch_mutex_wait等。Instruments中的System Trace工具則能夠記錄所有的底層系統調用。
記憶體
記憶體問題從來都是iOS app的老大難問題,搞不好程式就爆了。由於iOS系統沒有Swap文件(知道為啥不?留給懸念),在記憶體不足時會將只讀數據(例如code page)從記憶體中移出,需要的時候再從disk上讀如記憶體;可讀寫數據不會被系統從記憶體中移出,然而如果占用的記憶體達到一個閾值,系統會發出相應的通知和回調讓應用release對象以回收記憶體,如果仍然不能減少記憶體使用量,系統會直接關閉應用。尤其是iOS 5.0之後,如果你的app收到了memory warning,那麼腦袋也是和其他app一樣放在了案板上,隨時有可能被kill掉,並不是說一定會先Kill掉在後臺的app。
App使用的記憶體除了我們在堆上分配的記憶體外(+[NSobject alloc]/malloc
),還會有更多使用記憶體的地方,比如代碼和全局數據(TEXT和DATA),線程棧,圖片,view 的layer backing store等等。因此處理記憶體問題,絕不僅僅是我們開發app時儘量少申請記憶體那麼簡單。
現在有了超炫的ARC,記憶體問題相對少了很多,開發效率也得到了提高。但是很多公司的項目仍然由於歷史原因採用了手動管理記憶體,該做的活還是少不了。Xcode自帶的靜態分析功能可以幫你提前發現一些問題,然而有些記憶體問題是無法用靜態分析來發現的,例如我們不斷使用記憶體沒有及時釋放的問題,就無法使用靜態分析器分析出來。此時可以使用Instruments的Allocations和Leaks工具來檢查運行時的的記憶體使用以及泄露問題。
Allocations工具可以很直觀的反應app的記憶體使用情況,還有個很贊“Mark Heap”功能,在上圖左邊下半部分中的Heapshot Analysis中。例如你在進入一個頁面前點擊一下“Mark Heap”,然後再退回上一頁面點擊一下“Mark Heap”,如果你在進出這個頁面里所申請的記憶體都得到了合理的釋放,那麼堆的記憶體增長量就應該降至0(見上圖右下部分)。
另一種嚴重的記憶體使用問題是引用了已經釋放的記憶體,直接導致應用崩潰,而Allocation有一個選項Enable NSZombie detection能夠在應用使用已經釋放的記憶體時標註出來,同時顯示錯誤發生的調用棧信息。這為解決問題提供了最直接的幫助,當然缺點是必須能夠重現EXEC_BAD_ACCESS錯誤。
工具Leaks可以在應用運行時直接標示出存在記憶體泄露的代碼,如果發生了記憶體泄露,可以從泄露詳細信息中查看泄露的具體對象以及方法調用棧,大部分問題還是很好解決的。
圖形和動畫
圖形性能對用戶體驗有直接的影響,Instruments中的Core Animation工具用於測量物理機上的圖形性能,通過視圖的刷新頻率大小來判斷應用的圖形性能。例如一個複雜的列表滾動時它的刷新率應該努力趨近於60fps才能讓用戶覺得夠流暢,從這個數字也可以算出run loop最長的響應時間應該是16毫秒。
啟動Instruments的Core Animation工具後可以發現左下部分有一堆選項,我們來逐個介紹:
1) Color Blended Layers
Instruments可以在物理機上顯示出被混合的圖層Blended Layer(用紅色標註),Blended Layer是因為這些Layer是透明的(Transparent),系統在渲染這些view時需要將該view和下層view混合(Blend)後才能計算出該像素點的實際顏色,如果這種blended layer很多,那麼在滾動列表時就甭想有流暢的效果。
解決blended layer問題也很簡單,檢查紅色區域view的opaque屬性,記得設置成YES;檢查backgroundColor屬性是不是[UIColor clearColor]
,要知道背景顏色為clear color那可是圖形性能的大敵,基本意味著blended layer是跑不了的了,為什麼?自己思考一下:)
2) Color Hits Green and Misses Red
很多視圖Layer由於Shadow、Mask和Gradient等原因渲染很高,因此UIKit提供了API用於緩存這些Layer:[layer setShouldRasterize:YES],系統會將這些Layer緩存成Bitmap點陣圖供渲染使用,如果失效時便丟棄這些Bitmap重新生成。圖層Rasterization柵格化好處是對刷新率影響較小,壞處是刪格化處理後的Bitmap緩存需要占用記憶體,而且當圖層需要縮放時,要對刪格化後的Bitmap做額外計算。 使用這個選項後時,如果Rasterized的Layer失效,便會標註為紅色,如果有效標註為綠色。當測試的應用頻繁閃現出紅色標註圖層時,表明對圖層做的Rasterization作用不大。
3) Color Misaligned Images
Misaligned Image表示要繪製的點無法直接映射到頻幕上的像素點,此時系統需要對相鄰的像素點做anti-aliasing反鋸齒計算,增加了圖形負擔,通常這種問題出在對某些View的Frame重新計算和設置時產生的。
上圖中被標註為黃色的圖層,這是由於圖層顯示的是被縮放後的圖片,如果這些圖片是通過網路下載的,可以通過程式更新為確定的繪製大小來解決。還有些系統Navigation Bar和Tool Bar的背景圖片使用的是拉伸(Streched)圖片,也會被表示為黃色,這是屬於正常情況,通常無需修改。這種問題一般對性能影響不大,而是可能會在邊緣處虛化。
(4) Color Offscreen-Rendered Yellow
Offscreen-Rendering離屏渲染意思是iOS要顯示一個視圖時,需要先在後臺用CPU計算出視圖的Bitmap,再交給GPU做Onscreen-Rendering顯示在屏幕上,因為顯示一個視圖需要兩次計算,所以這種Offscreen-Rendering會導致app的圖形性能下降。
大部分Offscreen-Rendering都是和視圖Layer的Shadow和Mask相關,下列情況會導致視圖的Offscreen-Rendering: 1. 使用Core Graphics (CG開頭的類)。 2. 使用drawRect()方法,即使為空。 3. 將CALayer的屬性shouldRasterize設置為YES。 4. 使用了CALayer的setMasksToBounds(masks)和setShadow*(shadow)方法。 5. 在屏幕上直接顯示文字,包括Core Text。 6. 設置UIViewGroupOpacity。
這篇博文Designing for iOS: Graphics & Performance對offsreen以及圖形性能有個很棒的介紹,(5) Color Copied Images Copied Image選項可以標註應用繪製時被Core Animation複製的圖片,標註成藍綠色。雖然我在運行時遇到過,不過個人感覺對圖形性能影響不大。 (6) Color Immediately,Flash Updated Regions, Color OpenGL Fast Path Blue Color Immediately選項表示Instruments在做color-flush操作時取消10毫秒的延時。Flash Updated Regions選項用於用紅色示標示出在屏幕上使用GPU計算繪製的圖層。Color OpenGL Fast Path Blue選項用於用藍色標示出在屏幕上由OpenGL compositor繪製的內容。 這三個選項對圖形性能的分析意義較小,通常僅作為參考。
文件和網路I/O
如果需要對app的文件和網路I/O情況做分析,可以用到這三個Instruments工具System Usage、File Activity和Network。
工具System Usage可以統計出運行狀態下應用的文件和網路IO操作數據。例如我們發現應用啟動後又一個峰值,這可能存在問題,我們可以利用System Usage工具的詳細信息欄查看應用是由於對哪些文件的讀寫操作導致了峰值。
工具File Activity只能在模擬器中運行,因此數據採集可能不是非常準確。它同樣可以詳細給出讀取的文件屬性、大小、載入時間等信息,適合與System Usage配合使用。
Network工具則可以採集到應用的TCP/IP和UDP的使用信息(傳輸的數據量、當前所有TCP連接等),用得不多,做網路使用狀況分析時用用還行。
更多閱讀
涉及iOS App性能的知識很多,上面只是冰山一角,重點推薦WWDC的session。
WWDC 2012:
- 406: Adopting Automatic Reference Counting
- 238: iOS App Performance: Graphics and Animations
- 242: iOS App Performance: Memory
- 235: iOS App Performance: Responsiveness
- 409: Learning Instruments
- 706: Networking Best Practices
- 514: OpenGL ES Tools and Techniques
- 506: Optimizing 2D Graphics and Animation Performance
- 601: Optimizing Web Content in UIWebViews and Websites on iOS
- 225: Up and Running: Making a Great Impression with Every Launch
WWDC 2011:
- 105: Polishing Your App: Tips and tricks to improve the responsiveness and performance
- 121: Understanding UIKit Rendering
- 131 performance optimization on iphone os
- 308: Blocks and Grand Central Dispatch in Practice
- 323: Introducing Automatic Reference Counting
- 312: iOS Performance and Power Optimization with Instruments