我們?cè)谑褂媚承┏?jí)App時(shí),第一感受就是這款A(yù)pp怎么這么長(zhǎng)時(shí)間才啟動(dòng)起來(lái),等待的時(shí)間過(guò)長(zhǎng)可能我們自己就把App殺掉了,有的甚至可能沒(méi)有第二次啟動(dòng)機(jī)會(huì)就被卸載掉了,這個(gè)啟動(dòng)時(shí)間是非常影響用戶體驗(yàn)的。雖然我現(xiàn)在開發(fā)的App還不是超級(jí)App,啟動(dòng)時(shí)間也說(shuō)不上很慢,但是畢竟啟動(dòng)時(shí)間對(duì)于用戶來(lái)說(shuō)是越短越好,本著這個(gè)目標(biāo),我希望這次的優(yōu)化能夠更好的優(yōu)化App啟動(dòng)時(shí)間,給用戶帶來(lái)更好的用戶體驗(yàn)。
1、App啟動(dòng)類型
iPhone App的啟動(dòng)可以分為兩種類型:冷啟動(dòng)和熱啟動(dòng)。
1.1 冷啟動(dòng)
App啟動(dòng)前,其進(jìn)程已被殺死或根本就沒(méi)有在系統(tǒng)中存在,這種情況下點(diǎn)擊App圖標(biāo)啟動(dòng),就是一次冷啟動(dòng)過(guò)程。
1.2 熱啟動(dòng)
App啟動(dòng)后,將App切到后臺(tái),但是進(jìn)程沒(méi)有別殺死,進(jìn)程還在系統(tǒng)中存在,這種情況下重新點(diǎn)擊App圖標(biāo)進(jìn)入App,就是一次熱啟動(dòng)過(guò)程。
針對(duì)上述兩種啟動(dòng),我們一般優(yōu)化是指的優(yōu)化第一種類型的啟動(dòng)過(guò)程。
2、App啟動(dòng)過(guò)程
冷啟動(dòng)的過(guò)程就是從用戶點(diǎn)擊App圖標(biāo)開始,一直到- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法執(zhí)行完成為止。整個(gè)過(guò)程可以分為兩個(gè)階段,main()函數(shù)執(zhí)行之前和main()函數(shù)執(zhí)行之后。

2.1 main()函數(shù)執(zhí)行之前
- 加載可執(zhí)行文件(App的.o文件集合)
- 加載動(dòng)態(tài)鏈接庫(kù),進(jìn)行rebase指針調(diào)整和bind符號(hào)綁定
- Objc運(yùn)行時(shí)的初始處理,包括Objc相關(guān)類的注冊(cè)、Category注冊(cè)、selector唯一性檢查等
- 初始化,包括執(zhí)行+load()方法、attribute((constructor))修飾的函數(shù)調(diào)用、創(chuàng)建C++靜態(tài)全局變量
備注:
- rebase指針調(diào)整:修復(fù)指向當(dāng)前鏡像內(nèi)部的資源指針。
- binding符號(hào)綁定:指向鏡像外部的資源指針
- image 二進(jìn)制文件,包括可執(zhí)行文件或so文件,里邊是被編譯過(guò)的符號(hào)、代碼等。
- imageLoader 將image加載進(jìn)內(nèi)存,且每一個(gè)文件對(duì)應(yīng)一個(gè)ImageLoader實(shí)例來(lái)負(fù)責(zé)加載。
dyld(動(dòng)態(tài)連接器,the dynamic link editor)是一個(gè)專門用來(lái)加載動(dòng)態(tài)鏈接庫(kù)的庫(kù),dyld從可執(zhí)行文件的依賴開始,遞歸加載所有依賴的動(dòng)態(tài)鏈接庫(kù)。動(dòng)態(tài)鏈接庫(kù)包括:iOS中用到的所有系統(tǒng)framework,加載OC runtime方法的libobjc,系統(tǒng)級(jí)別的libSystem
相應(yīng)的,這個(gè)階段對(duì)于啟動(dòng)速度優(yōu)化來(lái)說(shuō),可以優(yōu)化的點(diǎn)包含以下幾個(gè)方面:
- 減少動(dòng)態(tài)庫(kù)加載。蘋果公司建議使用更少的動(dòng)態(tài)庫(kù),如果項(xiàng)目使用的動(dòng)態(tài)庫(kù)較多,可以將多個(gè)動(dòng)態(tài)庫(kù)合并使用,最多支持6個(gè)非系統(tǒng)動(dòng)態(tài)庫(kù)合為一個(gè)。
- 減少objc類和selector數(shù)量,刪除啟動(dòng)后不會(huì)使用的類或者方法,合并分類文件,刪除無(wú)用依賴庫(kù)。
- 將不必在+load()方法中做的事情延遲到+initialize()方法中。
- 刪減無(wú)用靜態(tài)變量,控制C++全局變量的數(shù)量。
2.2 main()函數(shù)執(zhí)行之后
這個(gè)階段是指從main()函數(shù)執(zhí)行開始,到Appdelegate的application:didFinishLaunchingWithOptions:方法執(zhí)行完成。App的啟動(dòng)邏輯、首屏渲染和業(yè)務(wù)代碼都是在這個(gè)階段,主要包括:
- 全局初始化配置,各種組件庫(kù)的初始化(crash統(tǒng)計(jì)、埋點(diǎn)統(tǒng)計(jì))
- 首頁(yè)數(shù)據(jù)請(qǐng)求、獲取和處理
- 首頁(yè)UI計(jì)算和渲染
對(duì)于第一條,我們要梳理出哪些配置和初始化工作是首屏渲染必要的,哪些是App啟動(dòng)所要求的,除此之外的配置和初始化都分別滯后到合適的階段執(zhí)行即可。
優(yōu)化點(diǎn):
- 梳理首屏渲染所不必須的依賴庫(kù)和功能邏輯,做延遲加載處理,比如放到首頁(yè)的
- (void)viewDidAppear:(BOOL)animated方法中去執(zhí)行。 - 在首頁(yè)控制器的
- (void)viewDidLoad和- (void)viewWillAppear:(BOOL)animated盡量的減少要做的事情,使首頁(yè)能夠盡快的加載顯示出來(lái)。 - 不使用
storyboard或xib構(gòu)建首頁(yè)UI視圖,使用純代碼來(lái)加載首頁(yè)。
3、App啟動(dòng)優(yōu)化
說(shuō)了這么多,App啟動(dòng)優(yōu)化的時(shí)間如何來(lái)衡量呢,這就要用到上面提到的時(shí)間T1和T2,下面我們來(lái)說(shuō)下如何來(lái)計(jì)算T1和T2。
測(cè)試使用機(jī)器:iPhone 7 Plus,iOS 10.3.2系統(tǒng),存儲(chǔ)容量32GB。
3.1 T1
main()之前的時(shí)間T1蘋果官方提供了一種方法,在Xcode的Edit Scheme...-->Run-->Arguments選項(xiàng)中設(shè)置Environment Variables,添加name為DYLD_PRINT_STATISTICS,Value為1。

項(xiàng)目使用真機(jī)運(yùn)行,在控制臺(tái)就可以看到如下log了。
Total pre-main time: 4.2 seconds (100.0%)
dylib loading time: 1.9 seconds (46.9%)
rebase/binding time: 1.6 seconds (39.3%)
ObjC setup time: 81.78 milliseconds (1.9%)
initializer time: 498.71 milliseconds (11.7%)
slowest intializers :
libSystem.B.dylib : 19.39 milliseconds (0.4%)
AppName : 918.63 milliseconds (21.6%)
可以看到Total pre-main time總耗時(shí)為4.2秒。
3.2 T2
main()之后到- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions是T2階段的時(shí)間,我們可以采用記住頭尾時(shí)間取差值的方式得到T2,main函數(shù)代碼如下:
extern CFAbsoluteTime startTime;
bool isBeauifulStr(char *str);
int main(int argc, char * argv[]) {
startTime = CFAbsoluteTimeGetCurrent();
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MIAppDelegate class]));
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中在最后加入如下代碼:
CFAbsoluteTime startTime;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
dispatch_async(dispatch_get_main_queue(), ^{
NSUInteger ms = (NSUInteger)((CFAbsoluteTimeGetCurrent() - startTime) * 1000);
NSLog(@"T2 = %lu ms", ms);
});
return YES;
}
項(xiàng)目啟動(dòng)后,輸出結(jié)果
T2 = 1190 ms
參考上面的優(yōu)化點(diǎn)逐條去對(duì)啟動(dòng)過(guò)程進(jìn)行優(yōu)化,然后對(duì)比得到的T1和T2的時(shí)間,就可以知道你的優(yōu)化效果到底如何了。
參考
iOS 程序 main 函數(shù)之前發(fā)生了什么
今日頭條iOS客戶端啟動(dòng)速度優(yōu)化
App 啟動(dòng)速度怎么做優(yōu)化與監(jiān)控?
優(yōu)化App的啟動(dòng)時(shí)間