一般而言,App的啓動時間指的是從用戶點擊App開始,到用戶看到第一個界面之間的時間。總結來書,App的啓動包括三個階段:
- main()函數執行前;
- main()函數執行後;
- 首屏渲染完成後。
main()函數執行前
在 main() 函數執行前,系統主要會做下面幾件事:
- 加載可執行文件 (App 的 .o文件集合)
- 加載動態鏈接庫,進行rebase 指針調整和bind符號綁定
- ObjC運行時的初始處理,包括ObjC相關類的註冊、category註冊、selector唯一性檢查等
- 初始化,包括可執行 +load()方法、attribute((constructor))修飾的函數的調用、創建C++靜態全局變量
對於這個階段啓動速度優化來說,可以做的事情如下
- 減少動態庫加載
- 減少加載啓動後不會去使用的類或者方法
- +load() 方法裏的內容可以放到首屏渲染完成後再執行,或者使用+initialize()方法替換掉
- 控制 C++全局變量的數量
main() 函數執行之後
main() 函數執行後的階段,指的是從main() 函數執行開始,到 appDelegate 的 didFinishLaunchingWithOptions 方法裏首屏渲染相關方法執行完成。
首頁業務代碼都是要在這個階段,也就是首屏渲染前執行的,只要包括:
- 首屏初始化所需配置文件的讀寫操作
- 首屏列表數據的讀取
- 首屏渲染的大量計算等
很多時候我們會把各種初始化工作放到這個階段執行,導致渲染完成滯後。我們應該從功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是App啓動必要的初始化功能,而哪些是只需要在對應功能開始使用時才需要初始化的。梳理完成後,將這些初始化功能分別放到適合的階段進行。
首屏渲染完成後
首屏渲染後的這個階段,主要完成的是,非首屏其他業務服務模塊的初始化、監聽的註冊、配置文件的讀取等等。從函數上來看,這個階段值得就是截止到 didFinishLaunchingWithOptions 方法作用域內執行首屏渲染之後的所用方法執行完成。簡單來說這個階段就是從渲染完成時開始,到 didFinishLaunchingWithOptions 方法作用域結束時結束。
對於App啓動速度的優化我們可以從 功能級別和方法級別 去優化
功能級別的啓動優化
優化思路是:main() 函數開始執行後到首屏渲染完成前只處理首屏相關的業務,其他非首屏業務的初始化、監聽註冊、配置文件讀取等都放到首屏渲染完成後。
方法級別的啓動優化
檢查首屏渲染完成前主線程上有哪些耗時方法,將沒有必要的耗時方法滯後或者異步執行。通常情況下,耗時較長的方法主要發生在計算大量數據的情況下,具體的體現是 加載、編輯、存儲圖片和文件等資源。
對App啓動速度的監控,只要有兩種手段
第一種方法是,定時抓取主線程上的方法調用堆棧,計算一段時間裏各個方法的耗時。 Xcode 工具套件裏自帶的 Time Profile 就是採用這種方式。
第二種方法是,對 objc_msgSend 方法進行 hook 來掌握所有方法的執行耗時。