應(yīng)用啟動(dòng)流程
iOS應(yīng)用啟動(dòng)可以分為pre-main階段和main()階段
pre-main階段
- 1.1. 加載應(yīng)用的可執(zhí)行文件
- 1.2. 加載動(dòng)態(tài)鏈接庫(kù)加載器dyld(dynamic loader)
- 1.3. dyld遞歸加載應(yīng)用所有依賴的dylib(dynamic library 動(dòng)態(tài)鏈接庫(kù))
main()階段
- 2.1. dyld調(diào)用main()
- 2.2. 調(diào)用UIApplicationMain()
- 2.3. 調(diào)用applicationWillFinishLaunching
- 2.4. 調(diào)用didFinishLaunchingWithOptions
- pre-main階段
對(duì)于pre-main階段,Apple提供了一種測(cè)量方法,在 Xcode 中 Edit scheme -> Run -> Auguments 將環(huán)境變量DYLD_PRINT_STATISTICS 設(shè)為1 。之后控制臺(tái)會(huì)輸出類似內(nèi)容:
Total pre-main time: 217.57 milliseconds (100.0%)
dylib loading time: 29.38 milliseconds (13.5%)
rebase/binding time: 12.15 milliseconds (5.5%)
ObjC setup time: 16.71 milliseconds (7.6%)
initializer time: 159.09 milliseconds (73.1%)
slowest intializers :
libSystem.B.dylib : 4.17 milliseconds (1.9%)
libMainThreadChecker.dylib : 34.80 milliseconds (15.9%)
libglInterpose.dylib : 53.05 milliseconds (24.3%)
libMTLInterpose.dylib : 14.19 milliseconds (6.5%)
ZhouDao : 89.54 milliseconds (41.1%)
這樣我們可以清晰的看到每個(gè)耗時(shí)了。
2.main()階段
mian()階段主要是測(cè)量mian()函數(shù)開始執(zhí)行到didFinishLaunchingWithOptions執(zhí)行結(jié)束的時(shí)間,我們直接插入代碼就可以了
CFAbsoluteTime StartTime;
int main(int argc, char * argv[]) {
StartTime = CFAbsoluteTimeGetCurrent();
到主UI框架的.m文件用extern聲明全局變量StartTime
extern CFAbsoluteTime StartTime;
在viewDidAppear函數(shù)里,再獲取一下當(dāng)前時(shí)間,與StartTime的差值即是main()階段運(yùn)行耗時(shí)。
double launchTime = (CFAbsoluteTimeGetCurrent() - StartTime);
[HomeViewController viewDidAppear:] launchTime:0.619898
改善啟動(dòng)時(shí)間
pre-main階段
在這一階段,我們能做的主要是優(yōu)化dylib
加載 Dylib
之前提到過加載系統(tǒng)的 dylib 很快,因?yàn)橛袃?yōu)化。但加載內(nèi)嵌(embedded)的 dylib 文件很占時(shí)間,所以盡可能把多個(gè)內(nèi)嵌 dylib 合并成一個(gè)來加載,或者使用 static archive。
使用 dlopen() 來在運(yùn)行時(shí)懶加載是不建議的,這么做可能會(huì)帶來一些問題,并且總的開銷更大。
Rebase/Binding
之前提過 Rebaing 消耗了大量時(shí)間在 I/O 上,而在之后的 Binding 就不怎么需要 I/O 了,而是將時(shí)間耗費(fèi)在計(jì)算上。所以這兩個(gè)步驟的耗時(shí)是混在一起的。
之前說過可以從查看 __DATA 段中需要修正(fix-up)的指針,所以減少指針數(shù)量才會(huì)減少這部分工作的耗時(shí)。對(duì)于 ObjC 來說就是減少 Class,selector 和 category 這些元數(shù)據(jù)的數(shù)量。從編碼原則和設(shè)計(jì)模式之類的理論都會(huì)鼓勵(lì)大家多寫精致短小的類和方法,并將每部分方法獨(dú)立出一個(gè)類別,其實(shí)這會(huì)增加啟動(dòng)時(shí)間。對(duì)于 C++ 來說需要減少虛方法,因?yàn)樘摲椒〞?huì)創(chuàng)建 vtable,這也會(huì)在 __DATA 段中創(chuàng)建結(jié)構(gòu)。雖然 C++ 虛方法對(duì)啟動(dòng)耗時(shí)的增加要比 ObjC 元數(shù)據(jù)要少,但依然不可忽視。
Objc setup
大部分ObjC初始化工作已經(jīng)在Rebase/Bind階段做完了,這一步dyld會(huì)注冊(cè)所有聲明過的ObjC類,將分類插入到類的方法列表里,再檢查每個(gè)selector的唯一性。
在這一步倒沒什么優(yōu)化可做的,Rebase/Bind階段優(yōu)化好了,這一步的耗時(shí)也會(huì)減少。
Initializers
到了這一階段,dyld開始運(yùn)行程序的初始化函數(shù),調(diào)用每個(gè)Objc類和分類的+load方法,調(diào)用C/C++ 中的構(gòu)造器函數(shù)(用attribute((constructor))修飾的函數(shù)),和創(chuàng)建非基本類型的C++靜態(tài)全局變量。Initializers階段執(zhí)行完后,dyld開始調(diào)用main()函數(shù)。
在這一步,我們可以做的優(yōu)化有:
- 1、少在類的+load方法里做事情,盡量把這些事情推遲到+initiailize
- 2、減少構(gòu)造器函數(shù)個(gè)數(shù),在構(gòu)造器函數(shù)里少做些事情
- 3、減少C++靜態(tài)全局變量的個(gè)數(shù)
main()階段的優(yōu)化
這一階段的優(yōu)化主要是減少didFinishLaunchingWithOptions方法里的工作,在didFinishLaunchingWithOptions方法里,我們會(huì)創(chuàng)建應(yīng)用的window,指定其rootViewController,調(diào)用window的makeKeyAndVisible方法讓其可見。由于業(yè)務(wù)需要,我們會(huì)初始化各個(gè)二方/三方庫(kù),設(shè)置系統(tǒng)UI風(fēng)格,檢查是否需要顯示引導(dǎo)頁(yè)、是否需要登錄、是否有新版本等,由于歷史原因,這里的代碼容易變得比較龐大,啟動(dòng)耗時(shí)難以控制。
所以,滿足業(yè)務(wù)需要的前提下,didFinishLaunchingWithOptions在主線程里做的事情越少越好。在這一步,我們可以做的優(yōu)化有:
- 1、梳理各個(gè)二方/三方庫(kù),找到可以延遲加載的庫(kù),做延遲加載處理,比如放到首頁(yè)控制器的viewDidAppear方法里。
- 2、梳理業(yè)務(wù)邏輯,把可以延遲執(zhí)行的邏輯,做延遲執(zhí)行處理。比如檢查新版本、注冊(cè)推送通知等邏輯。
- 3、避免復(fù)雜/多余的計(jì)算。
- 4、避免在首頁(yè)控制器的viewDidLoad和viewWillAppear做太多事情,這2個(gè)方法執(zhí)行完,首頁(yè)控制器才能顯示,部分可以延遲創(chuàng)建的視圖應(yīng)做延遲創(chuàng)建/懶加載處理。
- 5、首頁(yè)控制器用純代碼方式來構(gòu)建。
總結(jié)
總結(jié)起來,好像啟動(dòng)速度優(yōu)化就一句話:讓系統(tǒng)在啟動(dòng)期間少做一些事。當(dāng)然我們得先清楚工程里做的哪些事是在啟動(dòng)期間做的、對(duì)啟動(dòng)速度的影響有多大,然后case by case地分析工程代碼,通過放到子線程、延遲加載、懶加載等方式讓系統(tǒng)在啟動(dòng)期間更輕松些。
轉(zhuǎn)自:http://mobile.51cto.com/hot-584384.htm