iOS問題整理08----效能優化

根本停不下來發表於2018-08-07

本篇主要解決的問題

  • 優化你是從哪幾方面著手?
  • 在螢幕成像的過程中,CPU 和 GPU 分別負責處理哪些事情?針對介面的流暢度可以做什麼優化?
  • 如何減少啟動時間?
  • 安裝包如何瘦身?

優化你是從哪幾方面著手?

  • 記憶體優化
  • 卡頓優化
  • 耗電優化
    • 降低 CPU、GPU 功耗
    • 少用定時器
    • 優化 I/O 操作
      • 儘量不要頻繁寫入小資料,最好批量一次性寫入
      • 讀寫大量重要資料時,考慮使用dispatch_io,其提供了基於 GCD 的非同步操作檔案 I/O 的 API。用 dispatch_io 系統會優化磁碟訪問
      • 資料量比較大的,建議使用資料庫(比如 SQLite、CoreDota)
    • 網路優化
      • 減少、壓縮網路資料,比如 XML 換成 JSON,現在也有公司在 protobuf,比如上傳的檔案壓縮之後再傳
      • 多次請求結果是相同的,儘量使用快取,NSCache
      • 儘量使用斷點續傳,避免重複下載
      • 網路不可用的時候,不要嘗試執行網路請求
      • 設定合適的超時時間
    • 定位優化
    • 硬體檢測優化
      • 使用者移動、搖晃、傾斜裝置時,會產生動作(motion)事件,這些事件由加速計、陀螺儀、磁力計等硬體檢測。在不需要檢測的場合,應該及時關閉這些硬體。
  • 啟動時間優化
  • 安裝包大小優化

在螢幕成像的過程中,CPU 和 GPU 分別負責處理哪些事情?針對介面的流暢度可以做什麼優化?

螢幕上的任何內容要想顯示出來,都需要經過 CPU 和 GPU 的處理:

  • CPU 計算好數後提交給 GPU
  • GPU 對計算好的資料進行渲染,渲染成能顯示的資料並後放入幀快取
  • 視訊控制器器按照 VSync 訊號逐行讀取緩衝區中資料,經過數模轉換後傳遞給顯示器顯示

雙緩衝區機制

為解決效率問題,iOS 使用的是雙緩衝區機制。GPU 會先渲染好一幀放入一個緩衝區,供視訊控制器讀取;GPU 的下一幀渲染好之後會放入另一個緩衝區,這時視訊控制器的指標會指向這一個緩衝區。這樣做的弊端是:如果視訊控制器在讀取的過程中(即螢幕只顯示了一部分),GPU 完成了新一幀的繪製,那麼就會造成畫面撕裂的現象。為解決這個問題引入了 VSync 機制,當視屏控制器讀取完一幀之後才會發出 VSync 訊號,GPU 才會繪製新的一幀,完成後緩衝區才會更新。

按照 60FPS 的幀率,每隔 16ms 就會有一次 VSync 訊號。

卡頓的原因

VSync 訊號到來時,系統通過 CADisplayLink 等機制通知 App,App 開始在 CPU 中計算顯示內容並提交給 GPU,GPU 完成渲染後提交給緩衝區。

如果在這個過程中由來了第二個 VSync 訊號,也就是說 CPU 或者 GPU 的計算時間過長,在第二個垂直同步訊號來之前還沒有往緩衝區提交新內容。那麼,螢幕會保留之前的顯示內容,正在計算的這一幀,會等待下一次機會才能顯示。

這就是丟幀。就是說,卡頓的原因就是 CPU 或者 GPU 的計算時間過長,導致當垂直同步訊號到來時還沒有往雙緩衝區提交新的內容,螢幕會保留之前的內容,新內容只能等下一個垂直同步訊號才有機會顯示。

示意圖如下:

iOS問題整理08----效能優化

瞭解了上面的過程,我們就知道卡頓優化主要是要針對 CPU 與 GPU 進行優化。

CPU (Central Processing Unit,中央處理器)功能及優化

  • 物件的建立和銷燬
    • 儘量使用輕量級物件。比如在不需要響應觸控事件的時候使用 CALayer 代替 UIView
    • 使用 xib 會更消耗效能,在效能非常敏感的介面,可以放棄使用 xib
    • 儘量推遲物件的建立,把物件的建立分散到多個任務中去
  • 物件屬性的調整
    • UIView 的屬性調整遠大於一般物件的屬性調整,要儘量減少屬性的的調整
    • 儘量避免調整檢視的層次、新增和移除檢視
  • 佈局計算
    • UIView 屬性調整已經很消耗效能了,如果再修改frameboundstransform等屬性,那麼 CPU 和 GPU 又會重新計算和渲染,更消耗效能了,所以這些屬性要儘量減少調整次數。
    • AutoLayout 的帶來的 CPU 消耗會隨著檢視數量的增長呈指數級增長。
    • UIImageViewsize 最好與圖片的大小保持一致(避免更多計算)
  • 文字的計算和排版
    • 如果介面中有大量文字,那麼文字的寬高計算也會消耗大量資源。可以將寬高計算與繪製文字放到子執行緒中去處理。
  • 圖片的格式轉換和解碼
    • UIImage 建立圖片的時候,圖片並不會立即解碼,而是在 CALayer 提交到 GPU 的前一刻才進行解碼,這一步是在主執行緒中。要想繞開這個機制,常見的做法是自己在子執行緒中進行解碼,常見的第三方庫都有這個功能。
  • 影象的繪製(Core Graphics)
    • 一些以 CG 開頭的繪製影象的方法也可以放到子執行緒中去

GPU (Graphics Processing Unit,圖形處理器)及優化

  • 紋理的渲染
    • 儘量避免短時間內大量圖片的顯示,可以將多張圖片合成一張圖片
    • GPU 能處理的最大紋理尺寸是 4096x4096 一旦超過這個值,就會佔用 CPU 的資源進行處理
    • 減少檢視層級
    • 儘量避免透明度小於 1 的檢視
    • 儘量避免離屏渲染離屏渲染更詳細
      • 觸發離屏渲染的操作
        • 主動觸發:光layer.shouldRasterize
        • layer.shadowlayer.masklayer.borderlayer 設定圓角
        • 重寫記憶體惡鬼 drawRect: 方法時,檢視只要設定背景顏色,也會觸發離屏渲染
        • CAShapeLayer 的向量圖形顯示
        • Core Graphics API (核心繪圖)的繪製操作會導致 CPU 的離屏渲染。
      • 如何高效設定 ImageView 的圓角

卡頓檢測

  • 新增 Observer 到主執行緒 RunLoop 中,通過監聽 RunLoop 狀態切換的好時,以達到監控卡頓的目的。點選這裡檢視高清大圖

你在專案中是怎麼優化記憶體的?

tableview 卡頓的原因可能又哪些?你平時是怎麼優化的?

如何減少啟動時間?

APP 的啟動分為兩種:

  • 冷啟動:從零啟動
  • 熱啟動:APP 已經在記憶體中,在後臺活著,再次點選 APP 啟動 APP

APP 的啟動時間主要是針對冷啟動來說的。啟動時間主要由main()之前和main()之後兩部分組成。

分析main()之前的時間消耗

在 Edit scheme -> Run -> Arguments 中新增環境變數 DYLD_PRINT_STATISTICS 且設定值為 1。(按下 cmd + shift + , 快捷撥出 Edit scheme )

iOS問題整理08----效能優化

現在啟動程式就可以看到列印出來的資訊了。

iOS問題整理08----效能優化

如果設定 DYLD_PRINT_STATISTICS_DETAILS1,可以看到更詳細的資訊。

iOS問題整理08----效能優化

APP 的啟動過程可以分為如圖所示的三大階段:dyld 階段、runtime 階段、main 階段

iOS問題整理08----效能優化

啟動 APP 時,dyld 做的事情有:(dyld ,dynamic link editor, Apple 的動態聯結器,可以用來裝載 Mach-O 檔案,可執行檔案、動態庫等都屬於 Mach-O 檔案)

  • 裝載 APP 的可執行檔案,同時會遞迴載入所有依賴的動態庫
  • dyld 把可執行檔案、動態庫都裝載完畢後,會通知 runtime 進行下一步的處理。

啟動APP時,runtime 所做的事情有:

  • 呼叫 map_images 進行可執行檔案內容的解析和處理
  • load_images 中呼叫 call_load_methods,呼叫所有 ClassCategory+load 方法
  • 進行各種 objc 結構的初始化(註冊 objc 類、初始化類物件等等)
  • 呼叫 C++ 靜態初始化器和 __attribute__((constructor))修飾的函式

到此為止,可執行檔案和動態庫中所有的符號(Class, Protocol, Selector, IMP,...)都已經按格式成功載入到記憶體中,被 runtime 所管理。

main() 階段:

main 函式的實現很簡單,如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil NSStringFromClass([AppDelegate class]));
    }
}
複製程式碼

當程式碼走到 UIApplicationMain 函式時,就會走到 Appdelegate 類裡。然後就會呼叫 application:didFinishLaunchingWithOptions:

App啟動過程中每一個步驟都會影響啟動效能,但是有些部分所消耗的時間少之又少,另外有些部分根本無法避免,考慮到投入產出比,我們只列出我們可以優化的部分:

  • dyld
    • 減少動態庫(定期清理不必要的動態庫)
    • 減少 Objc 類、分類的數量(定期清理不必要的類、分類)
    • 減少 C++ 虛擬函式數量
    • Swift 儘量使用 struct
  • runtime
    • 儘量不要用到 +load 方法
    • 儘量不要用到__attribute__((constructor))的C函式
    • 也儘量不要用到C++的靜態物件
  • main
    • 在不影響使用者體驗的前提下,儘可能將一些操作延遲,不要全部都放在 finishLaunching 方法中
    • 按需載入

安裝包如何瘦身?

iOS問題整理08----效能優化

相關文章