App的啟動(dòng)時(shí)間是衡量一個(gè)App性能的重要指標(biāo)绩脆,也是用戶(hù)對(duì)于App的第一印象。一般來(lái)說(shuō)啟動(dòng)時(shí)間(小于400ms是最佳的百姓,并且系統(tǒng)限制了啟動(dòng)時(shí)間不可以大于20s
楣颠,否則會(huì)因?yàn)?a target="_blank">watchdog(看門(mén)狗)機(jī)制被殺掉。
前面我們結(jié)合objc
和dyld
夏志,從代碼層面分析過(guò)App的啟動(dòng)加載過(guò)程乃坤,下面我們從宏觀層面,探討一下App的啟動(dòng)流程沟蔑。啟動(dòng)流程一般劃分為 pre-main
(main函數(shù)之前) 和 main函數(shù)之后 湿诊。
一、pre-main
通過(guò)配置DYLD_PRINT_STATISTICS
環(huán)境變量瘦材,我們可以直觀的查看pre-main
數(shù)據(jù)厅须。
打印結(jié)果:
Total pre-main time: 660.38 milliseconds (100.0%)
dylib loading time: 259.55 milliseconds (39.3%) //動(dòng)態(tài)庫(kù)framework,蘋(píng)果建議不大于6個(gè)
rebase/binding time: 30.13 milliseconds (4.5%) //ASLR修正內(nèi)部指針食棕,綁定外部指針
ObjC setup time: 159.08 milliseconds (24.0%) //oc類(lèi)注冊(cè)
initializer time: 211.60 milliseconds (32.0%) //load方法
slowest intializers :
libSystem.B.dylib : 7.29 milliseconds (1.1%)
libMainThreadChecker.dylib : 28.86 milliseconds (4.3%)
AFNetworking : 88.42 milliseconds (13.3%)
FProject : 139.13 milliseconds (21.0%) //主程序
該階段各個(gè)時(shí)期的任務(wù)以及優(yōu)化方法:
變量名 | 介紹 | 備注 |
---|---|---|
dylib loading |
Dyld從主執(zhí)行文件的header獲取到需要加載的所依賴(lài)動(dòng)態(tài)庫(kù)列表朗和,然后它需要找到每個(gè) dylib,而應(yīng)用所依賴(lài)的 dylib 文件可能會(huì)再依賴(lài)其他 dylib簿晓,所以所需要加載的是動(dòng)態(tài)庫(kù)列表一個(gè)遞歸依賴(lài)的集合 | 1.盡量不使用內(nèi)嵌(embedded)的dylib例隆,加載內(nèi)嵌dylib性能開(kāi)銷(xiāo)較大;2.合并已有的dylib和使用靜態(tài)庫(kù)(static archives)抢蚀,減少dylib的使用個(gè)數(shù)镀层;3.懶加載dylib,但是要注意dlopen()可能造成一些問(wèn)題皿曲,且實(shí)際上懶加載做的工作會(huì)更多 |
rebase/binding |
1. Rebase在Image內(nèi)部調(diào)整指針的指向唱逢。在過(guò)去,會(huì)把動(dòng)態(tài)庫(kù)加載到指定地址屋休,所有指針和數(shù)據(jù)對(duì)于代碼都是對(duì)的坞古,而現(xiàn)在地址空間布局是隨機(jī)化,所以需要在原來(lái)的地址根據(jù)隨機(jī)的偏移量做一下修正劫樟。2. Bind是把指針正確地指向Image外部的內(nèi)容痪枫。這些指向外部的指針被符號(hào)(symbol)名稱(chēng)綁定,dyld需要去符號(hào)表里查找叠艳,找到symbol對(duì)應(yīng)的實(shí)現(xiàn) | 1.減少ObjC類(lèi)(class)奶陈、方法(selector)、分類(lèi)(category)的數(shù)量附较;2.減少C++虛函數(shù)的的數(shù)量(創(chuàng)建虛函數(shù)表有開(kāi)銷(xiāo))吃粒;3.使用Swift structs(內(nèi)部做了優(yōu)化,符號(hào)數(shù)量更少) |
ObjC setup |
1.注冊(cè)O(shè)bjc類(lèi) (class registration)拒课;2.把category的定義插入方法列表 (category registration)徐勃;3.保證每一個(gè)selector唯一 (selector uniquing) | 減少 Objective-C Class事示、Selector、Category 的數(shù)量僻肖,可以合并或者刪減一些OC類(lèi) |
initializer |
1.Objc的+load()函數(shù)肖爵;2.C++的構(gòu)造函數(shù)屬性函數(shù);3.非基本類(lèi)型的C++靜態(tài)全局變量的創(chuàng)建(通常是類(lèi)或結(jié)構(gòu)體) | 1.少在類(lèi)的+load方法里做事情臀脏,盡量把這些事情推遲到+initiailize劝堪;2.減少構(gòu)造器函數(shù)個(gè)數(shù),在構(gòu)造器函數(shù)里少做些事情谁榜;3.減少C++靜態(tài)全局變量的個(gè)數(shù) |
如果要獲取更新詳細(xì)的時(shí)間信息幅聘,可以使用環(huán)境變量
DYLD_PRINT_STATISTICS_DETAILS
。
二窃植、main函數(shù)后
main函數(shù)之后的帝蒿,一般指從didFinishLaunchingWithOptions到viewDidAppear方法結(jié)束,這也是runloop的UIInitializationRunLoopMode循環(huán)結(jié)束巷怜。
這里我們可以直接打印時(shí)間葛超,直觀的處理時(shí)間,也可以使用工具BLStopwatch延塑,他的使用也很簡(jiǎn)單绣张,不過(guò)這個(gè)代碼很多沒(méi)更新了。
簡(jiǎn)單的使用
[[BLStopwatch sharedStopwatch]start];
[[BLStopwatch sharedStopwatch]splitWithDescription:@"didFinishLaunchingWithOptions"];
//刷新時(shí)間
[[BLStopwatch sharedStopwatch]refreshMedianTime];
[[BLStopwatch sharedStopwatch]splitWithDescription:@"viewDidAppear"];
[[BLStopwatch sharedStopwatch]stopAndPresentResultsThenReset];
優(yōu)化方案: 視圖盡量懶加載关带;發(fā)揮cpu能力侥涵,多線(xiàn)程初始化非主線(xiàn)程任務(wù);減少storyboard和xib使用宋雏。
參考
load耗時(shí)監(jiān)測(cè)
A0PreMainTime 的使用
#pre-main階段耗時(shí)檢測(cè)
pod 'A0PreMainTime/PreMainTime'
#業(yè)務(wù)時(shí)間度量
pod 'A0PreMainTime/TimeMonitor'