作者基於之前自己所寫的Swift項目--仿照推特客戶端用純Swift寫的一個項目,目前已經公開放在GitHub上(https://github.com/waitwalker/MyTwitter); 接口用Python寫的幾個(https://github.com/waitwalker/MyTwitterAPI),目前所實現的功能是登錄,註冊,發推,首頁列表等功能,其他頁面都是一些假數據.這裡不主要分析項目了,有時間在詳細說一下.本文的主要重點是說說自己對性能調優--tableView控制項優化的一點理解,有些問題理解的不是很透徹,希望大家能多給些意見,建議,謝謝.下圖是整個項目的總覽(文章中多是gif動態,尺寸較大,使用流量看得慎重):
項目總覽1.gif
項目總覽2.GIF
有一句話說:過早的優化是萬惡之源, 過早並不是開發過程的早期,而是在還沒弄清楚業務需求的情形下去做所謂的優化,有時候會適得其反——費時、費力、不討好。正確的方法是,先有質量地實現你的需求,寫夠測試用例,然後做profile去找到性能的瓶頸,考慮究竟哪些地方應該優化,應該如何優化,哪些不應該優化.
文章結構.png
作者對一些顯示的垂直信號,水平信號原理不是了解,大家如果想深入了解的可以查一下相關資料,相關大神也有總結,或許從硬體底層優化可能效果會更好.造成tableView卡頓的原因,從硬體上來說無非就兩個,一個是CPU原因,一個是GPU原因.如果CPU核數較多,並發處理問題的能力也就越強,處理大量計算也不在話下;如果GPU顯存夠大,渲染能力足夠強,處理複雜圖形界面也就得心應手.但是,硬體的配置是有限度的,我們的目標是在有限度的硬體上,讓其發揮最大限度的作用.這個也就是造成tableView卡頓的程序原因(軟體原因)--卡住了主線程,本文將主要討論是從程序角度討論怎麼優化tableView問題.
基礎最基本的就是減少cell的自定義類型,重用cell,每次只繪製屏幕顯示cell的數量,其它cell從緩存中取.這些基礎大家應該比我了解,這裡不再陳述了.
1. 減輕CPU負荷我們知道CPU的主要負責快速調度任務,大量計算工作,所以在tableView快速滾動的過程中讓CPU的計算量降低是優化應該考慮的方向.下面總結了三個方面來儘可能的降低CPU計算:
大家都已經知道tableView的代理回調方法中,先調用的是返回cell高度的方法,然後在返回實例化cell的方法.我們可以在返回cell高度時,提前計算好cell的高度,緩存到數據源模型中.例如:MTTHomeModel對應的是首頁cell的數據模型,我們可以看到下面兩個變量,是來存儲cell的高度和內容高度的,:
var cellHeight:CGFloat?
var contentHeight:CGFloat?
在獲取數據後臺數據的時候,把cell高度計算出來,緩存起來:
homeModel.contentHeight = self.calculateTextHeight(text: homeModel.contentTextString!) + 150
if(homeModel.retwitterType?.count)! > Int(0)
{
homeModel.cellHeight = 255 + homeModel.contentHeight! - 150
} else{
homeModel.cellHeight = 230 + homeModel.contentHeight! - 150 + 15
}
在返回cell高度的方法中,直接讀取緩存的高度,而不需要在重新計算了.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
if self.homeDataArray != nil
{
let homeModel = homeDataArray![indexPath.row]
return homeModel.cellHeight!
} else
{
return 300
}
}
之前看到一些大家分享的相關資料,通過Interface知道xib或者storyboard本身就是一個xml文件,添加刪除控制項必然中間多了一個encode/decode過程,增加了cpu的計算量.並且 還要避免臃腫的 XIB 文件,因為XIB文件在主線程中進行加載布局.當用到一些自定義View或者XIB文件時,XIB的加載會把所有內容加載進來,如果XIB裡面的一些控制項並不會用到,這就可能造成一些資源的消耗浪費.網上有說:Storyboard 沒這個問題,只會按需加載,這個作者還沒有去考證.
自動布局就是給控制項添加約束,約束最終還是轉換成frame.所以,在滿足業務需求情況下,如果圖層層次較為複雜,要儘量減少自動布局約束,轉為手動計算布局,大量的約束重疊也會增加cpu的計算量
作者在獲取到數據源時,每次都重新布局控制項,這個也是一個重要開銷,也是接下來需要優化的方向.
private funclayoutSubview(homeModel:MTTHomeModel) -> Void
{
topLineView?.snp.makeConstraints({ (make) in
make.left.right.top.equalTo(0)
make.height.equalTo(0.3)
})
}
UIKit的工作基本上都是在主線程上進行,界面繪製,用戶輸入響應等等.當所有的代碼邏輯都放在主線程時,某些耗時任務可能會卡住主線程造成程序無法響應,流暢度降低等問題;在主線程中繪製大量界面圖層,網絡I/O,磁碟I/O等都可以造成界面卡頓現象.
下面我們通過Xcode自帶的調試工具Instruments來看看項目界面的流暢度,及其一些建議,Instruments給我提供了各種各樣的調試查看工具,下面簡單介紹一下:
1)Blank: 創建一個空的模板,可以從Library庫中添加其他模板.
2)Activity Monitor: 監控進程級別的CPU,內存,磁碟,網絡使用情況,可以得到你的應用程式在手機運行時總共佔用的內存大小.
3)Allocations: 跟蹤過程的匿名虛擬內存和堆的對象提供類名和可選保留/釋放歷史,可以檢測每一個堆對象的分配內存情況.
4)Cocoa Layout : 觀察NSLayoutConstraint對象的改變,幫助我們判斷什麼時間什麼地點的constraint是否合理.觀察約束變化,找出布局代碼的問題所在.
5)Core Animation: 這個模塊顯示程序顯卡性能以及CPU使用情況,查看界面流暢度.
6)CoreData: 這個模塊跟蹤Core Data文件系統活動.
7)Counters : 收集使用時間或基於事件的抽樣方法的性能監控計數器(PMC)事件.
8)Energy Log: 耗電量監控.
9)File Activity: 檢測文件創建,移動,變化,刪除等.
10)Leak: 一般的措施內存使用情況,檢查洩漏的內存,並提供了所有活動的分配和洩漏模塊的類對象分配統計信息以及內存地址歷史記錄.
11)Metal System Trace: Metal API是apple 2014年在ios平臺上推出的高效底層的3D圖形API,它通過減少驅動層的API調用CPU的消耗提高渲染效率.
12)Network: 用連結工具分析你的程序如何使用TCP/IP和UDP/IP連結.
13)SceneKit: 3D性能狀況分析.
14)System Trace: 系統跟蹤,通過顯示當前被調度線程提供綜合的系統表現,顯示從用戶到系統的轉換代碼通過兩個系統調用或內存操作.
15)System Usage: 這個模板記錄關於文件讀寫,sockets,I/O系統活動,輸入輸出.
16)Time Profiler(時間探查): 執行對系統的CPU上運行的進程低負載時間為基礎採樣.
17)Zombies: 測量一般的內存使用,專注於檢測過度釋放的野指針對象,也提供對象分配統計,以及主動分配的內存地址歷史.
本文主要使用的是Instruments中的第5個工具:Core Animation(圖形性能),這個模塊顯示程序顯卡性能以及CPU使用情況,查看界面流暢度.
首先我們必須要把源碼安裝到測試設備上,1)連接Xcode運行程序;2)然後選擇快捷鍵(Command + Control + i)調出Instruments,選擇Core Animation.打開後我們可以看到Debug Options裡面有多個調試選項,下面我們挨個儘量來分析看一下:
這個選項選項基於渲染程度對屏幕中的混合區域進行綠到紅的高亮顯示,越紅表示性能越差,會對幀率等指標造成較大的影響.紅色通常是由於多個半透明圖層疊加引起.
作者項目可能項目比較簡單,圖層也不是很複雜,所以通過Color Blended Layers查看,深紅色並不是很明顯,在快速滑動的過程中,幀率依然能夠保持在55+以上,並且圖層中也沒有大量的深紅色區域出現.
ColorBlendedLayers1-1.png
ColorBlendedLayers1-2-.gif
當UIView.layer.shouldRasterize = YES 時,耗時的圖片繪製會被緩存,並當做一個簡單的扁平圖片來呈現.這時候,如果頁面的其他區塊(比如 UITableViewCell 的復用)使用緩存直接命中,就顯示綠色,反之,如果不命中,這時就顯示紅色.紅色越多,性能越差.因為柵格化生成緩存的過程是有開銷的,如果緩存能被大量命中和有效使用,則總體上會降低開銷,反之則意味著要頻繁生成新的緩存,這會讓性能問題雪上加霜.
ColorHitsGreenandMissesRed2-1.gif
這裡筆者還要提一下圖片的加載方式,我們知道圖片的一般加載方式有兩種:imageNamed 和imageWithContentsOfFile;它們的不同在於前者會對圖片進行緩存,而後者只是簡單的從文件加載文件.如果你加載的是大圖,並且只會用到一次,比如歡迎引導圖,那麼就沒必要緩存這個圖片,可以使用[UIImage imageWithContentsOfFile:],用完就釋放了.如果會多次使用到一張圖時,用[UIImage imageNamed:] 就會高效很多,因為這種加載圖片方式有一個緩存機制.YYImage實現原理應該就是後面這種思路,自己手動添加緩存.
2.3 Color Copied Images對於 GPU 不支持的色彩格式的圖片只能由 CPU 來處理,把這樣的圖片標為藍色.藍色越多,性能越差.因為,我們不希望在滾動視圖的時候,由 CPU 來處理圖片,這樣可能會對主線程造成阻塞.
ColorCopiedImages3-1.gif
通常 Core Animation Instruments 以每毫秒 10 次的頻率更新圖層調試顏色。對某些效果來說,這顯然太慢了.這個選項就可以用來設置每幀都更新(可能會影響到渲染性能,而且會導致幀率測量不準,所以不要一直都設置它).
ColorImmediately4-1.gif
這個選項檢查了圖片是否被縮放,以及像素是否對齊.被放縮的圖片會被標記為黃色,像素不對齊則會標註為紫色.黃色,紫色越多,性能越差.
ColorMisalignedImages5-1.gif
這裡UI在切圖的時候儘量切得尺寸和你控制項的尺寸保持一致,儘量讓圖片保持原始尺寸.筆者這裡所用圖片幾乎全部拉伸,由於圖片都是從本地加載的,沒有經過處理.
2.6 Color Offscreen-Rendered Yellow這個選項會把那些離屏渲染的圖層顯示為黃色.黃色越多,性能越差.這些顯示為黃色的圖層很可能需要用 shadowPath 或者 shouldRasterize 來優化.
ColorOffscreen-RenderedYellow6-1.gif
離屏渲染,即 Off-Screen Rendering.與之相對的是 On-Screen Rendering,即在當前屏幕渲染,意思是渲染操作是用於在當前屏幕顯示的緩衝區進行.那麼離屏渲染則是指圖層在被顯示之前是在當前屏幕緩衝區以外開闢的一個緩衝區進行渲染操作.
離屏渲染需要多次切>換上下文環境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束以後,將離屏緩衝區的渲染結果顯示到屏幕上又需要將上下文環境從離屏切換到當前屏幕,而上下文環境的切換是一項高開銷的動作.
一般對控制項屬性操作會觸發離屏渲染:
1)陰影(UIView.layer.shadowOffset/shadowRadius/…)
2)圓角(當 UIView.layer.cornerRadius 和UIView.layer.maskToBounds 一起使用時)
3)圖層蒙板
在實際開發中應儘量避免觸發離屏渲染.
這個選項會把任何直接使用OpenGL 繪製的圖層顯示為藍色.藍色越多,性能越好.如果僅僅使用 UIKit 或者 Core Animation 的 API,那麼不會有任何效果.如果使用 GLKView 或者 CAEAGLLayer,那如果不顯示藍色塊的話就意味著你正在強制 CPU 渲染額外的紋理,而不是繪製到屏幕.
總結任何優化都是以業務需求為前提,在滿足基本需求的情況下,逐步提高代碼的質量,提升程序性能,不僅是自我能力的表現,也能從中獲得一些收穫及成就感.以上優化方向思路也是在前人總結的基礎上,作者在自己的項目中的簡單應用,裡面還有許許多多需要改進提升的地方,也希望大家能給一些深層次上的建議意見.
https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
作者:偶爾登南山
連結:https://www.jianshu.com/p/5182234b2e1c
更多推薦: