場景
假設一個這樣的場景,早高峰趕公交,沒帶公交卡,掏出手機打開App1準備掃碼上車,結果App半天進不去,后面的人都怒視著你,然后果斷打開App2,秒開,那么下一次你會選擇哪個App呢,所以說App的啟動速度不僅決定了用戶體驗,更是決定了它是否能過贏得更多客戶
App啟動時都做了啥呢
啟動的方式
- 冷啟動:從零開始啟動App
- 熱啟動:App已經在內存中,后臺存活著,用戶重新啟動進入App的過程,該過程做的事情非常少,啟動很快
優化主要從冷啟動的角度出發,主要分為main函數執行前和main函數執行后
圖片.png
main函數執行之前
-
dyld(全名
dynamic link editor
Apple的動態鏈接器,可以用來加載Mach-O
文件) 裝載App的可執行文件,同時遞歸加載所有依賴的動態庫 - 當dyld文件將可執行文件和動態庫加載完畢后,通知
RunTime
進行下一步處理 -
RunTime
做的事情有
3.1. 調用map_images
進行可執行文件內容的解析和處理
3.2. 在load_images
中調用call_load_methods
調用類class
和分類category
的+load
方法
3.3. 進行各種objc
結構的初始化(注冊objc
類,初始化類結構)
3.4. 調用C++靜態初始化器和__attribute__((constructor))
修飾的函數
4.到此為止,可執行文件和動態庫中所有的符號(Class , Protocol, Selector, IMP,...)都按格式加載到內存中被RunTime
管理
優化方案
- 減少動態庫、合并一些動態庫(定期清理不必要的動態庫)
- 減少
Objc
類、分類的數量、減少Selector
數量(定期清理不必要的類分類)- 減少C++虛函數數量
- Swift盡量使用
struct
- 用
+ initialize
方法和dispatch_once
取代所有的__attribute__((constructor))
、C++靜態編譯器、Objc
的+load
方法
main函數執行之后
- 從
main()
函數執行開始,到appDelegate
的didFinishLaunchingWithOptions
方法里首屏渲染相關方法執行完成階段
- 首屏初始化所需配置文件的讀寫操作;
- 首屏列表大數據的讀取;
- 首屏渲染的大量計算等。
優化: 功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是 App 啟動必要的初始化功能,而哪些是只需要在對應功能開始使用時才需要初始化的。梳理完之后,將這些初始化功能分別放到合適的階段進行
-
appDelegate
的didFinishLaunchingWithOptions
方法作用域內執行首屏渲染之后的所有方法執行完成階段
- 非首屏其他業務服務模塊的初始化、監聽的注冊、配置文件的讀取等
- 第三方SDK初始化
優化:
main()
函數開始執行后到首屏渲染完成前只處理首屏相關的業務,其他非首屏業務的初始化、監聽注冊、配置文件讀取等都放到首屏渲染完成后去做.
將沒必要的耗時方法滯后或者異步執行
啟動時間獲取
- 通過添加環境變量可以打印出App的啟動時間(
Edit scheme
->Run
->Arguments
->Environment Variables
)DYLD_PRINT_STATISTICS
設置為1
Total pre-main time: 5.6 seconds (100.0%)
dylib loading time: 4.3 seconds (76.4%)
rebase/binding time: 1.1 seconds (20.3%)
ObjC setup time: 107.58 milliseconds (1.8%)
initializer time: 71.88 milliseconds (1.2%)
slowest intializers :
libSystem.B.dylib : 22.57 milliseconds (0.3%)
打印的是執行main函數之前的耗時
- Time Profiler定時抓取主線程上的方法調用堆棧,計算一段時間里各個方法的耗時
- fishhook https://github.com/facebook/fishhook
-
戴銘的GCDFetchFeed 在需要檢測耗時時間的地方調用
[SMCallTrace start]
,結束時調用stop
和save
就可以打印出方法的調用層級和耗時了。你還可以設置最大深度和最小耗時檢測,來過濾不需要看到的信息