更多內容請挪步我的博客
前言
有一句箴言說:“如果一個產品不注重性能,再華麗也是難用?!?——什么?你沒聽說過?好吧,這句話是我說的。
要解決一個問題,大多數(shù)情況不是我們不知道如何解決,而是我們不知道問題在哪,所以定位問題是非常重要的。突然想到美?。跦ouse]豪斯醫(yī)生(啊,其實我好久沒有看過美劇了,之前太忙了,請忽略我的廢話),很多疾病也是很容易解決的,只是那些平庸的醫(yī)生找不到癥結所在。在 iOS 的世界里,查找“病因”的關鍵工具就是 Instruments 了。這篇文章主要說說如何通過 Instruments 找到問題所在。
另外,由于最近為[伯樂在線]翻譯一篇文章(該文章尚未發(fā)布),作者 Russ Bishop 談到自己查找內存泄漏的一次痛苦經歷,所以單開一篇文章說如何檢測內存泄漏,如果你對下文中的 Allocations & Leaks 章節(jié)感覺一覽未盡的話,接下來我會寫一篇關于調試內存泄漏的文章,里面會講的更加詳盡。
自己設計和編碼的項目[LEAF Photo](一款拼圖應用)基本完成等待上線前的收尾工作中,其中有一些優(yōu)化還是挺有代表性的,就拿它作為 Demo 吧。
<br />
Time Profiler
調優(yōu)一般我都是從 Time Profiler 開始

打開 Time Profiler,將控制面板 Call Tree 中的 Seperate by Thread、 Hide System Libraries 勾選,Invert Call Tree 也可以選中,查看耗時最多的調用,看看是否有可以調整的地方,下面以我的 Demo 為例,談談如何調整。
顯然,[MainTplTableViewCell setTplCoverModel] 中有些圖片處理的耗時操作,這個可以放到線程中去做,使其不影響主線程。
NSString *coverPath = [[NSBundle mainBundle] pathForResource:cover ofType:@"jpg"];
UIImage *tplImage = [UIImage imageWithContentsOfFile:coverPath];
UIImage *scaledImage = [tplImage scaleImageAspectFitToSize:toImageView.bounds.size];
[toImageView setImage:scaledImage];
將上面的方法放到線程中,另外由于 imageWithContentsOfFile 方法不會進行緩存,所以加載圖片后將其加到緩存 (類型是 NSCache )中。
UIImage *tplImage = [self.cache objectForKey:indexPath];
if (tplImage) {
[toImageView setImage:tplImage];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *coverPath = [[NSBundle mainBundle] pathForResource:cover ofType:@"jpg"];
UIImage *tplImage = [UIImage imageWithContentsOfFile:coverPath];
UIImage *scaledImage = [tplImage scaleImageAspectFitToSize:toImageView.bounds.size];
[self.cache setObject:scaledImage forKey:indexPath];
dispatch_async(dispatch_get_main_queue(), ^{
[toImageView setImage:scaledImage];
});
});
這樣優(yōu)化后,setTplCoverModel 方法由原來的 32ms 降低到了 2ms。
通過 Time Profiler 找到最耗時的調用,不影響主線程的操作放到應當線程中;需要優(yōu)化算法的通過算法降低時間復雜度;經常訪問的數(shù)據(jù)放到緩存中,通過以上方法做調整。
<br />
Allocations & Leaks
運行 Leaks 檢查是否存在內存泄漏。但是 ARC 出現(xiàn)后,即使檢測過程中覆蓋面夠大,大多數(shù)情況下也是查不出什么泄漏的,除非你犯了太明顯的錯誤,所以 Leaks 工具可能還不夠用。
<br />
之后運行 Allocations,點擊所有的頁面,看看進入每個頁面后退出,內存是否會下降回或者接近進入該頁面前的內存數(shù)。如果進入某個頁面,再退出,發(fā)現(xiàn)內存沒有減少回初始狀態(tài),說明操作的頁面應該是有內存泄漏的。
那么如何哪里泄漏呢,這里可以使用 Generation Analysis。發(fā)現(xiàn)有問題后,一定要記住重現(xiàn)步驟,每次按照重現(xiàn)步驟操作,操作中在進入懷疑有內存泄漏的頁面 Mark Gerneration,等待幾秒鐘,頁面加載完畢后,再退出該頁面,再 Mark Gerneration 下,如果 Snapshot 的 Growth 和 Persistent 一直增長,說明肯定是有問題的。如果這個時候你打開對應頁面的代碼,隨便看一眼,看到某個 Block 中有循環(huán)引用或者其他錯誤,那么恭喜你問題找到了。但是如果問題太隱蔽怎么辦?看我的下一篇文章吧。
<br />
Core Animation
運行 Core Animation 查看幀率,如果滾動時幀率達不到60 fps 都是有調整的空間。下面按照設置面板中的 Debug Options 分別說明。
<br />
Color Blended Layers
紅色的區(qū)域說明該 View 是透明的,系統(tǒng)需要對這些 View 和下層的 View 混合(Blend)才能計算出實際顏色。所以應當盡量將紅色的 View 設置為不透明。
這個優(yōu)化,我覺得主要是要和設計師協(xié)作才能有好的效果。例如:如果圖片是 PNG 的,且部分地方透明,可以的話可以將透明部分的顏色設置為背景色(這樣做,是不是逼死設計師,平白無辜的給人家添了些工作,估計還要費一番口舌去解釋原因)
再者,可以看下手機 QQ 的應用,打開左側邊欄,有很多應用為了美觀在滾動區(qū)域設置了背景圖,但是手機 QQ 遇到這個問題是怎么解決的?看下你就知道了,上方不滾動區(qū)域有背景圖,但是滾動區(qū)域是不加背景圖的。這樣能保證滾動區(qū)域的視圖是非透明的。
<br />
Color Hits Green and Misses Red
如果設置了柵格 shouldRasterized 屬性,那么這里會使用紅色對柵格化進行高亮。
cell.layer.shouldRasterize = YES;
如果在離屏渲染中使用了圓角、陰影等效果,在對應視圖的層上開啟柵格化屬性,該屬性在繪制圖層時會對圖像進行緩存,所以對其進行柵格化后,頁面剛加載的時候會和未優(yōu)化時幀率差不太多,但是滾動中因為進行了緩存,效率有所提高。所以說柵格化就是一把雙刃劍。
<br />
Color Copied Images
這里指圖片的顏色或者格式 GPU 處理不了,這時圖片需要使用 CPU 處理。如果遇到這種情況,請檢查下圖片格式或者顏色空間設置。
<br />
Color Immediately
通常 Core Animatin Instruments 以每毫秒10次的頻率更新圖層,對于某些效果來說,這個速度可能會比較慢,所以設置該選項后每秒都會刷新,但是該選項開啟后會影響到渲染性能,導致幀率測量不準確,所以不需要時請不要勾選此項
<br />
Color Misaligned Images
設置該選項后,被繪制為黃色的 View 是被縮放的,被設置為洋紅色的 View 是像素沒有對齊(像素對齊指繪制的點無法直接映射到屏幕的像素點上)的。
處理黃色視圖方法:圖片大小和控件大小不一致,可以使用 Core Graphics 中的 UIGraphicsBeginImageContextWithOptions 方法將圖片縮放到控件大小,縮放圖片的調用應放到線程中,避免阻塞主線程。
處理洋紅色視圖方法:盡管 Core Graphics 中的坐標都是浮點型的,但是請盡量將坐標設置為整型的,例如在 [LEAF Photo]項目中很多視圖的坐標是計算出來的,難免就出現(xiàn)浮點型,這時可以使用 ceilf、floorf和 CGRectIntegral 將小數(shù)整數(shù)化。
說一段痛苦的經歷,在修改 Color Misaligned Images 問題時,我經歷過類似 Bishop (前言提到的翻譯文章作者) 查找內存泄漏時遭遇的不幸,當我把圖片進行縮放后放到 ImageView 中,發(fā)現(xiàn) ImageView 仍然顯示黃色,Debug 和 UI Debug 還有 Reveal 查看視圖的大小和圖片的大小都是一致的,但是仍然顯示成黃色,于是還特意查了很多其他資料,看看是否有其他原因導致黃色,但是都無果。經過一個多小時的折騰,我發(fā)現(xiàn)這個問題可能是 Instruments 的問題。
我為何會懷疑 Instruments 呢,因為我將模擬器(說明下用模擬器做性能測試,是在各種方式嘗試后仍找不到原因才使用的,希望這里不要誤導了大家,性能方面的測試還是要在真機上運行滴)的 Debug -> Color Misaligned Images 勾選后,可以直接查看視圖問題,這時開啟 Reveal,在 Reveal 中查看是沒有黃色的,那么是不是因為 Reveal 的 Debug 沒有顯示有問題的視圖的功能呢?于是我把代碼退回到優(yōu)化前的版本,可以看到 Reveal 是有洋紅色和黃色的,這時 Instruments 也是顯示有問題,再使用修改后的版本,Reveal 上不再有洋紅色和黃色,Instruments 上黃色還存在,但是洋紅色比之前少了。所以暫時作罷,不再查了,因為調整后幀率已經從52 - 56 增長到 57 - 60 fps了。
<br />
Color Offscreen-Rendered Yellow
離屏渲染。網上很多說如何優(yōu)化離屏渲染的文章,不詳述了,主要是滾動視圖中直接使用 cornerRadius 處理圓角會有性能問題,很多人寫了 Core Graphics 繪制圓角等方法解決;
view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
view.layer.shadowRadius = 5.0f;
view.layer.shadowOpacity = 0.6;
使用陰影的話要添加 shadowPath 來優(yōu)化。
view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];
<br />
Color OpenGL Fast-Path Blue
勾選該選項后會對任何直接使用OpenGL繪制的圖層進行高亮。如果僅使用 UIKit 或者 Core Animation 的 API,那么不會有任何效果。如果使用 GLKView 或者 CAEAGLLayer,那如果不顯示藍色塊的話就意味著你正在強制 CPU 渲染額外的紋理,而不是繪制到屏幕。
<br />
Flash Updated Regions
勾選該選項會對重繪的內容高亮成黃色(也就是任何在軟件層面使用 Core Graphics 繪制的圖層)。這種繪圖的速度很慢。如果頻繁發(fā)生這種情況的話,這意味著有一個隱藏的 bug 或者說通過增加緩存或者使用替代方案會有提升性能的空間。
上面的描述是不是太術語化了,舉個例子可能就好明白了。例如寫一個時鐘的應用,大多數(shù)時間只需要調整秒針的位置,這個時候只需要重會秒針的區(qū)域,但是如果重繪的區(qū)域顯示為這個表盤的話就需要優(yōu)化下代碼了。
OpenGL ES Analysis
如果想知道 GPU 的利用率,無疑這個工具是可以幫助你,另外它還可用來判斷和 GPU 相關的動畫性能。
右側邊欄的統(tǒng)計數(shù)據(jù)有一些有用的指標
Renderer Utilization
這個值大于50%,看下是否有離屏渲染問題或者 Blended View 把混合視圖盡量設置為不透明。
Tiler Utilization
這個值大于50%,看看是否能將圖層數(shù)減少,是否有多余的圖層刪除或者合并。
測試設備
注意的是測試性能一定要使用真機,不同型號的 iPhone 測試結果也會不同,例如 iPhone 5s 上測試滾動幀率在50多 fps,在 iPhone 4上的幀率可能也就 30 - 40。
另外 Insturments 有時跟矯情的小娘們一樣耍耍小性兒,開始按鈕不能點擊,或者連接了真機后,設備中的真機不可用。這時嘗試拔掉手機連線,重啟手機,還不管用再重啟下電腦和 Xcode。
寫到最后
最后說明下,以上文章出自多年積累,某些概念因年舊沒有記錄出處了。另外,如果有寫的不對的地方請指正。