本篇主要解決的問題
- 優化你是從哪幾方面著手?
- 在螢幕成像的過程中,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 的處理:
雙緩衝區機制
為解決效率問題,iOS 使用的是雙緩衝區機制。GPU 會先渲染好一幀放入一個緩衝區,供視訊控制器讀取;GPU 的下一幀渲染好之後會放入另一個緩衝區,這時視訊控制器的指標會指向這一個緩衝區。這樣做的弊端是:如果視訊控制器在讀取的過程中(即螢幕只顯示了一部分),GPU 完成了新一幀的繪製,那麼就會造成畫面撕裂的現象。為解決這個問題引入了 VSync
機制,當視屏控制器讀取完一幀之後才會發出 VSync
訊號,GPU 才會繪製新的一幀,完成後緩衝區才會更新。
按照 60FPS 的幀率,每隔 16ms 就會有一次 VSync 訊號。
卡頓的原因
當 VSync
訊號到來時,系統通過 CADisplayLink
等機制通知 App,App 開始在 CPU 中計算顯示內容並提交給 GPU,GPU 完成渲染後提交給緩衝區。
如果在這個過程中由來了第二個 VSync
訊號,也就是說 CPU
或者 GPU
的計算時間過長,在第二個垂直同步訊號來之前還沒有往緩衝區提交新內容。那麼,螢幕會保留之前的顯示內容,正在計算的這一幀,會等待下一次機會才能顯示。
這就是丟幀。就是說,卡頓的原因就是 CPU 或者 GPU 的計算時間過長,導致當垂直同步
訊號到來時還沒有往雙緩衝區提交新的內容,螢幕會保留之前的內容,新內容只能等下一個垂直同步訊號才有機會
顯示。
示意圖如下:
瞭解了上面的過程,我們就知道卡頓優化主要是要針對 CPU 與 GPU 進行優化。
CPU (Central Processing Unit,中央處理器)功能及優化
- 物件的建立和銷燬
- 儘量使用輕量級物件。比如在不需要響應觸控事件的時候使用
CALayer
代替UIView
- 使用
xib
會更消耗效能,在效能非常敏感的介面,可以放棄使用xib
- 儘量推遲物件的建立,把物件的建立分散到多個任務中去
- 儘量使用輕量級物件。比如在不需要響應觸控事件的時候使用
- 物件屬性的調整
UIView
的屬性調整遠大於一般物件的屬性調整,要儘量減少屬性的的調整- 儘量避免調整檢視的層次、新增和移除檢視
- 佈局計算
- UIView 屬性調整已經很消耗效能了,如果再修改
frame
、bounds
、transform
等屬性,那麼 CPU 和 GPU 又會重新計算和渲染,更消耗效能了,所以這些屬性要儘量減少調整次數。 AutoLayout
的帶來的CPU
消耗會隨著檢視數量的增長呈指數級增長。UIImageView
的size
最好與圖片的大小保持一致(避免更多計算)
- UIView 屬性調整已經很消耗效能了,如果再修改
- 文字的計算和排版
- 如果介面中有大量文字,那麼文字的寬高計算也會消耗大量資源。可以將寬高計算與繪製文字放到子執行緒中去處理。
- 圖片的格式轉換和解碼
UIImage
建立圖片的時候,圖片並不會立即解碼,而是在CALayer
提交到 GPU 的前一刻才進行解碼,這一步是在主執行緒中。要想繞開這個機制,常見的做法是自己在子執行緒中進行解碼,常見的第三方庫都有這個功能。
- 影象的繪製(Core Graphics)
- 一些以 CG 開頭的繪製影象的方法也可以放到子執行緒中去
GPU (Graphics Processing Unit,圖形處理器)及優化
- 紋理的渲染
- 儘量避免短時間內大量圖片的顯示,可以將多張圖片合成一張圖片
- GPU 能處理的最大紋理尺寸是
4096x4096
一旦超過這個值,就會佔用 CPU 的資源進行處理 - 減少檢視層級
- 儘量避免透明度小於 1 的檢視
- 儘量避免離屏渲染、離屏渲染更詳細
- 觸發離屏渲染的操作
- 主動觸發:光柵化
layer.shouldRasterize
layer.shadow
、layer.mask
、layer.border
、layer 設定圓角
- 重寫記憶體惡鬼 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 )
現在啟動程式就可以看到列印出來的資訊了。
如果設定 DYLD_PRINT_STATISTICS_DETAILS
為 1
,可以看到更詳細的資訊。
APP 的啟動過程可以分為如圖所示的三大階段:dyld 階段、runtime 階段、main 階段
啟動 APP 時,dyld
做的事情有:(dyld ,dynamic link editor, Apple 的動態聯結器,可以用來裝載 Mach-O 檔案,可執行檔案、動態庫等都屬於 Mach-O 檔案)
- 裝載 APP 的可執行檔案,同時會遞迴載入所有依賴的動態庫
- 當
dyld
把可執行檔案、動態庫都裝載完畢後,會通知runtime
進行下一步的處理。
啟動APP時,runtime 所做的事情有:
- 呼叫
map_images
進行可執行檔案內容的解析和處理 - 在
load_images
中呼叫call_load_methods
,呼叫所有Class
和Category
的+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
方法中 - 按需載入
- 在不影響使用者體驗的前提下,儘可能將一些操作延遲,不要全部都放在