場景
假設(shè)一個這樣的場景,早高峰趕公交,沒帶公交卡,掏出手機打開App1準備掃碼上車,結(jié)果App半天進不去,后面的人都怒視著你,然后果斷打開App2,秒開,那么下一次你會選擇哪個App呢,所以說App的啟動速度不僅決定了用戶體驗,更是決定了它是否能過贏得更多客戶
App啟動時都做了啥呢
啟動的方式
- 冷啟動:從零開始啟動App
- 熱啟動:App已經(jīng)在內(nèi)存中,后臺存活著,用戶重新啟動進入App的過程,該過程做的事情非常少,啟動很快
優(yōu)化主要從冷啟動的角度出發(fā),主要分為main函數(shù)執(zhí)行前和main函數(shù)執(zhí)行后

圖片.png
main函數(shù)執(zhí)行之前
-
dyld(全名
dynamic link editorApple的動態(tài)鏈接器,可以用來加載Mach-O文件) 裝載App的可執(zhí)行文件,同時遞歸加載所有依賴的動態(tài)庫 - 當dyld文件將可執(zhí)行文件和動態(tài)庫加載完畢后,通知
RunTime進行下一步處理 -
RunTime做的事情有
3.1. 調(diào)用map_images進行可執(zhí)行文件內(nèi)容的解析和處理
3.2. 在load_images中調(diào)用call_load_methods調(diào)用類class和分類category的+load方法
3.3. 進行各種objc結(jié)構(gòu)的初始化(注冊objc類,初始化類結(jié)構(gòu))
3.4. 調(diào)用C++靜態(tài)初始化器和__attribute__((constructor))修飾的函數(shù)
4.到此為止,可執(zhí)行文件和動態(tài)庫中所有的符號(Class , Protocol, Selector, IMP,...)都按格式加載到內(nèi)存中被RunTime管理
優(yōu)化方案
- 減少動態(tài)庫、合并一些動態(tài)庫(定期清理不必要的動態(tài)庫)
- 減少
Objc類、分類的數(shù)量、減少Selector數(shù)量(定期清理不必要的類分類)- 減少C++虛函數(shù)數(shù)量
- Swift盡量使用
struct- 用
+ initialize方法和dispatch_once取代所有的__attribute__((constructor))、C++靜態(tài)編譯器、Objc的+load方法
main函數(shù)執(zhí)行之后
- 從
main()函數(shù)執(zhí)行開始,到appDelegate的didFinishLaunchingWithOptions方法里首屏渲染相關(guān)方法執(zhí)行完成階段
- 首屏初始化所需配置文件的讀寫操作;
- 首屏列表大數(shù)據(jù)的讀??;
- 首屏渲染的大量計算等。
優(yōu)化: 功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是 App 啟動必要的初始化功能,而哪些是只需要在對應(yīng)功能開始使用時才需要初始化的。梳理完之后,將這些初始化功能分別放到合適的階段進行
-
appDelegate的didFinishLaunchingWithOptions方法作用域內(nèi)執(zhí)行首屏渲染之后的所有方法執(zhí)行完成階段
- 非首屏其他業(yè)務(wù)服務(wù)模塊的初始化、監(jiān)聽的注冊、配置文件的讀取等
- 第三方SDK初始化
優(yōu)化:
main()函數(shù)開始執(zhí)行后到首屏渲染完成前只處理首屏相關(guān)的業(yè)務(wù),其他非首屏業(yè)務(wù)的初始化、監(jiān)聽注冊、配置文件讀取等都放到首屏渲染完成后去做.
將沒必要的耗時方法滯后或者異步執(zhí)行
啟動時間獲取
- 通過添加環(huán)境變量可以打印出App的啟動時間(
Edit scheme->Run->Arguments->Environment Variables)DYLD_PRINT_STATISTICS設(shè)置為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%)
打印的是執(zhí)行main函數(shù)之前的耗時
- Time Profiler定時抓取主線程上的方法調(diào)用堆棧,計算一段時間里各個方法的耗時
- fishhook https://github.com/facebook/fishhook
-
戴銘的GCDFetchFeed 在需要檢測耗時時間的地方調(diào)用
[SMCallTrace start],結(jié)束時調(diào)用stop和save就可以打印出方法的調(diào)用層級和耗時了。你還可以設(shè)置最大深度和最小耗時檢測,來過濾不需要看到的信息