廢話不說,直接上乾貨!
一、APP啟動過程
1.解析Info.plist
- 載入相關資訊,例如如閃屏
- 沙箱建立、許可權檢查
2.Mach-O載入
- 如果是胖二進位制檔案,尋找合適當前CPU類別的部分
- 載入所有依賴的Mach-O檔案(遞迴呼叫Mach-O載入的方法)
- 定位內部、外部指標引用,例如字串、函式等
- 執行宣告為__attribute__((constructor))的C函式
- 載入類擴充套件(Category)中的方法
- C++靜態物件載入、呼叫ObjC的 +load 函式
3.程式執行
- 呼叫main()
- 呼叫UIApplicationMain()
- 呼叫applicationWillFinishLaunching
二、監測啟動時間
1.開啟時間分析功能
在Xcode的選單中選擇Project→Scheme→Edit Scheme
...,然後找到Run → Environment Variables → +
,新增name為DYLD_PRINT_STATISTICSvalue
為1
的環境變數。
執行後會在控制檯看到如下內容:
Total pre-main time: 94.33 milliseconds (100.0%)
dylib loading time: 61.87 milliseconds (65.5%)
rebase/binding time: 3.09 milliseconds (3.2%)
ObjC setup time: 10.78 milliseconds (11.4%)
initializer time: 18.50 milliseconds (19.6%)
slowest intializers :
libSystem.B.dylib : 3.59 milliseconds (3.8%)
libBacktraceRecording.dylib : 3.65 milliseconds (3.8%)
MyTest : 7.09 milliseconds (7.5%)
複製程式碼
得出結論:
- main()函式之前總共使用了94.33ms
- 在94.33ms中,載入動態庫用了61.87ms,指標重定位使用了3.09ms,ObjC類初始化使用了10.78ms,各種初始化使用了18.50ms。
- 在初始化耗費的18.50ms中,用時最多的三個初始化是libSystem.B.dylib、libBacktraceRecording.dylib以及MyTest。
三、影響APP啟動效能的因素
1.main()
函式之前耗時的影響因素
- 動態庫載入越多,啟動越慢。
- ObjC類越多,啟動越慢
- C的
constructor
函式越多,啟動越慢 - C++靜態物件越多,啟動越慢
- ObjC的
+load
越多,啟動越慢
實驗證明,在ObjC類的數目一樣多的情況下,需要載入的動態庫越多,App啟動就越慢。同樣的,在動態庫一樣多的情況下,ObjC的類越多,App的啟動也越慢。需要載入的動態庫從1個上升到10個的時候,使用者幾乎感知不到任何分別,但從10個上升到100個的時候就會變得十分明顯。同理,100個類和1000個類,可能也很難查察覺得出,但1000個類和10000個類的分別就開始明顯起來。
同樣的,儘量不要寫__attribute__((constructor))
的C函式,也儘量不要用到C++的靜態物件;至於ObjC的+load
方法,似乎大家已經習慣不用它了。任何情況下,能用dispatch_once()
來完成的,就儘量不要用到以上的方法。
2.main()
函式之後耗時的影響因素
從main()
函式開始至didFinishLaunchingWithOptions
結束,我們統一稱為main()
函式之後的部分。
- 執行
main()
函式的耗時 - 執行
didFinishLaunchingWithOptions
的耗時 rootViewController
及其childViewController
的載入、view
及其subviews
的載入
3.didFinishLaunchingWithOptions
的耗時
其實 didFinishLaunchingWithOptions
方法裡我們一般都有以下的邏輯:
- 初始化第三方 SDK
- 配置 APP 執行需要的環境,比如
rootViewController
載入 - 自己的一些工具類的初始化
四、制定優化目標
分為四個部分:
main()
函式之前main()
函式之後至didFinishLaunchingWithOptions
完成- App完成所有本地資料的載入並將相應的資訊展示給使用者
- App完成所有聯網資料的載入並將相應的資訊展示給使用者
五、優化方案
1.main()
函式之前的優化
- 移除不需要用到的動態庫。
- 移除不需要用到的類。
- 合併功能類似的類和擴充套件(Category)。
- 壓縮資源圖片。
- 減少load 方法中執行的程式碼。
2.main()
函式之後至didFinishLaunchingWithOptions
完成的優化
這一步主要優化didFinishLaunchingWithOptions
方法中的程式碼和rootViewController
載入的優化。
didFinishLaunchingWithOptions
優化
對於didFinishLaunchingWithOptions
,這裡面的初始化是必須執行的,但是我們可以適當的根據功能的不同對應的適當延遲啟動的時機。對於我們專案,我將初始化分為三個型別:
-
日誌、統計等必須在 APP 一起動就最先配置的事件
-
專案配置、環境配置、使用者資訊的初始化 、推送、IM等事件
-
其他 SDK 和配置事件
對於第一類,由於這類事件的特殊性,所以必須第一時間啟動,仍然把它留在 didFinishLaunchingWithOptions
裡啟動。第二類事件,這些功能在使用者進入 APP 主體的之前是必須要載入完的,所以我們可以把它放在第二批,也就是使用者已經看到廣告頁面,再進行廣告倒數計時的時候再啟動。第三類事件,由於不是必須的,所以我們可以放在第一個介面渲染完成以後的 viewDidAppear
方法裡,這裡完全不會影響到啟動時間。
rootViewController
載入優化
這個只能看具體的邏輯和業務了,儘量把不必要的業務和網路請求等延後載入。