一母赵、?啟動過程分析
1眼溶、解析Info.plist
a.加載相關(guān)信息碍现,例如閃屏
b.沙箱建立、權(quán)限檢查
2米奸、Mach-O加載
Mach-O 文件:我們寫的程序想要跑起來昼接,肯定它的可執(zhí)行文件格式要被操作系統(tǒng)所理解。比如ELF是Linux下的可執(zhí)行文件格式悴晰,那么對于OS X / iOS來說慢睡,Mach-O 是其可執(zhí)行文件格式逐工。
Mach-O格式主要包括以下幾種文件類型:
Executable:應(yīng)用的主要二進制
Dylib:動態(tài)鏈接庫
Bundle:不能被鏈接,只能在運行時使用dlopen加載
Image:包含Executable漂辐、Dylib和Bundle
Framework:包含Dylib泪喊、資源文件和頭文件的文件夾
如果是胖二進制文件,尋找合適當(dāng)前CPU類別的部分
加載所有依賴的Mach-O文件(遞歸調(diào)用Mach-O加載的方法)
定位內(nèi)部髓涯、外部指針引用袒啼,例如字符串、函數(shù)等
執(zhí)行聲明為__attribute__((constructor))的C函數(shù)
加載類擴展(Category)中的方法
C++靜態(tài)對象加載纬纪、調(diào)用ObjC的?+load?函數(shù)
3蚓再、程序執(zhí)行
調(diào)用main()
調(diào)用UIApplicationMain()
調(diào)用applicationWillFinishLaunching
二、測量App的啟動時間
iOS App的啟動有兩種方式:冷啟動和熱啟動包各,下面對這兩種啟動方式進行簡單介紹摘仅。
冷啟動:App第一次啟動或者是被上滑Kill掉之后,再次從點擊App應(yīng)用圖標到App完全啟動顯示主頁面為止的過程成為冷啟動问畅。
熱啟動:當(dāng)用戶從當(dāng)前App按下HOME鍵退出后娃属,系統(tǒng)并不會立即kill掉App的進程,還會繼續(xù)后臺執(zhí)行一段時間(至于什么時候會被kill护姆,完全取決于系統(tǒng)內(nèi)存等資源的使用情況矾端,理想情況下可以保持后臺運行15min)。然后當(dāng)用戶再次點擊App圖標時签则,會很快加載到上次停留的頁面须床,幾乎不需要進行資源載入,這種情況下的啟動成為熱啟動渐裂。
下面我們對于App啟動時間的測量只討論在冷啟動情形下豺旬。根據(jù)蘋果官方介紹,iOS應(yīng)用的啟動分為兩個階段:pre-main和main柒凉,所以App啟動的總時間為:Total Time = pre-main Time + main Time族阅。
Pre-main 階段
Pre-mian 階段即main()函數(shù)被調(diào)用之前的加載時間,包括dylib動態(tài)庫的加載膝捞、Mach-O文件加載坦刀、Rebase/Bining、Objective-C Runtime 加載等蔬咬。該過程如下圖所示:
main 階段
main 階段指的是從調(diào)用main()函數(shù)開始鲤遥,調(diào)用UIApplicationMain()創(chuàng)建UIApplication,AppDelegate,直到回調(diào)App代理方法applicationDidBecomeActive:為止林艘,其中包括application:didFinishLaunchingWithOptions方法中創(chuàng)建keyWindow盖奈、三方sdk和其他初始化項所消耗的時間,也包含RootViewController 的 ChildViewController的view完全顯示出來所花費的時間狐援。該過程如下圖所示:
main 階段時間測量
那么對于pre-main?和?main?啟動階段的耗時如何測量呢钢坦?蘋果已為我們提供了一個簡單易用的測試方法:在Xcode中選中項目的Scheme→?Edit Scheme...→究孕,然后選擇Run→Arguments→Environment Variables中添加Name =?DYLD_PRINT_STATISTICSvalue,Value = 1 的環(huán)境變量爹凹。如下圖所示:
然后重新運行項目厨诸,注意控制臺下面的打印輸出,格式為:
Totalpre-main time:655.68milliseconds (100.0%)
dylib loading time:205.67milliseconds (31.3%)
rebase/binding time:320.95milliseconds (48.9%)
ObjCsetup time:63.07milliseconds (9.6%)
initializer time:65.85milliseconds (10.0%)
? ? ? ? ? slowest intializers :
libSystem.B.dylib :3.43milliseconds (0.5%)
libMainThreadChecker.dylib :15.59milliseconds (2.3%)
century :76.56milliseconds (11.6%)
Totalpre-main time:? 表示pre-main階段總的時間消耗為655.68毫秒
dylib loading time: 動態(tài)庫加載時間
rebase/binding time: 重定位指針指向/指向鏡像外部內(nèi)容 所需時間
ObjCsetup time:ObjC初始化時間
initializer time: 其他初始化時間的總和
slowest intializers : 最慢的三個初始化分別是libSystem.B.dylib禾酱、libMainThreadChecker.dylib微酬、century(項目名稱)
main 階段時間測量
由于main階段涉及到大量的自定義代碼,app主體頁面的初始化宇植、前期配置得封、三方庫初始化等大量邏輯代理,iOS系統(tǒng)沒有直接的測量方式指郁,所以只能通過打點的方式計算啟動開始到啟動結(jié)束的時間差即可忙上。例如下面示例代碼:
// main.m 文件
#import"AppDelegate.h"
externCFAbsoluteTimeStartTime;
intmain(intargc,char* argv[]) {
// 記錄開始時間
StartTime =CFAbsoluteTimeGetCurrent();
@autoreleasepool{
returnUIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegateclass]));
? ? }
}
// AppDelegate.m 文件
CFAbsoluteTimeStartTime;
- (void)applicationDidBecomeActive:(UIApplication*)application {
dispatch_async(dispatch_get_main_queue(), ^{
NSUIntegermilliseconds = (NSUInteger)((CFAbsoluteTimeGetCurrent() - StartTime) *1000);
NSLog(@"Loading done in %lu ms", milliseconds);
? ? });
}
影響啟動性能的因素
接下來我們討論在App啟動的pre-main階段和mian階段,影響啟動性能的多種因素闲坎。
Pre-main 階段
動態(tài)庫加載越多疫粥,啟動越慢。
ObjC類越多腰懂,啟動越慢
C的constructor函數(shù)越多梗逮,啟動越慢
C++靜態(tài)對象越多,啟動越慢
ObjC的+load方法使用越多绣溜,啟動越慢
我們盡量不要寫__attribute__((constructor))的C函數(shù)慷彤,也盡量不要用到C++的靜態(tài)對象;至于ObjC的+load方法怖喻,似乎大家已經(jīng)習(xí)慣不用它了底哗。任何情況下,能用dispatch_once()來完成的锚沸,就盡量不要用到以上的方法跋选。
main 階段
執(zhí)行main()函數(shù)的耗時
執(zhí)行application:didFinishLaunchingWithOptions的耗時
rootViewController及其childViewController的加載、view及其subviews的加載
一般的App的主體UI架構(gòu)是哗蜈,UITabbarViewController?作為?keyWindow的根控制器前标,然后包含多個ChildViewController作為每個tab的根控制器。然而在didFinishLaunchingWithOptions代理方法中各控制器的初始化順序是怎樣的呢距潘?
答案是:
-[MQQTabBarController viewDidLoad]
-[MQQTab1ViewController viewDidLoad]
-[AppDelegate application:didFinishLaunchingWithOptions:]
-[MQQTab2ViewController viewDidLoad]?(點擊了第二個tab之后加載)
-[MQQTab3ViewController viewDidLoad]?(點擊了第三個tab之后加載)
從上面的加載過程可以看出炼列,在main()方法之后的啟動中,只要是對頁面的渲染音比,要減少這個階段的耗時需要在viewDidLoad方法中做優(yōu)化唯鸭,減少不必要的邏輯,視圖盡量使用懶加載硅确,使用異步方式加載網(wǎng)絡(luò)數(shù)據(jù)目溉。
三、啟動時間優(yōu)化
蘋果建議在400ms內(nèi)完成pre-main 階段的啟動菱农,App整體的啟動時間不能超過20s缭付,否則系統(tǒng)會被kill掉進程。
由于每個App的體量和類型不一樣循未,對應(yīng)啟動過程中配置的邏輯處理復(fù)雜程度也是皆不相同陷猫,所有沒有一個標準的時間來衡量啟動時間是否達到最優(yōu)。但是可以從用戶體驗上下功夫預(yù)加載占位視圖的妖,數(shù)據(jù)緩存等都可以營造出加載速度提升的感覺绣檬。
下面從實踐的角度分析可優(yōu)化的部分:
pre-main 階段優(yōu)化
1、移除不需要的動態(tài)庫framework
2嫂粟、移除不需要的類娇未、未使用的變量、方法等
對應(yīng)項目中大量的文件星虹,可以使用工具快速掃描整個項目找出未使用的類零抬、變量、或方法宽涌。這里推薦一個開源工具 Fui 能準確的完成此任務(wù)平夜,不足之處在于它處理不了動態(tài)庫或靜態(tài)庫中提供的類,和C++ 的類模板卸亮。
Fui使用方法:
在終端中cd到項目根目錄忽妒,然后執(zhí)行fui find,就可以得到一個列表兼贸,可根據(jù)這個列表在Xcode中手動檢查并刪除無用代碼段直。
3、合并功能類似的類和擴展(Category)
4、+load方法中做的事情可以使用dispatch_once()來代替扶镀,因為main()方法被調(diào)用之前就會加載+load方法呻率,會影響pre-main階段啟動事件。
5京闰、壓縮資源圖片(對要求不高的圖片可做壓縮處理)
main 階段優(yōu)化
1、優(yōu)化application: didFinishLaunchingWithOptions:內(nèi)部邏輯
各種業(yè)務(wù)請求配置更新甩苛,多個配置更新可合并為單個請求蹂楣,部分業(yè)務(wù)的配置延后更新。
對于新版本引導(dǎo)讯蒲、廣告閃屏邏輯痊土,需要重構(gòu)邏輯清晰
數(shù)據(jù)遷移,對太舊的無用的邏輯可以刪除墨林,不需要立即處理的數(shù)據(jù)赁酝,改為后臺加載
2犯祠、優(yōu)化rootViewController加載:在啟動過程中只加載tabbarVc,主Vc即可酌呆,而且主Vc中的ViewDidLoad方法中也只加載需要立即顯示出來的view衡载,其他視圖均使用懶加載,數(shù)據(jù)進行異步加載隙袁。
文章轉(zhuǎn)載:https://www.dazhuanlan.com/2020/01/04/5e1055a4e07fa/?__cf_chl_jschl_tk__=7750f38bebb1f21fdaddcddca0415ee71d31c736-1614321013-0-AWpmiwwC2bq_8PHZ6dbnAI7dbzqLC36vJnCCmhi6ThuE9vMnd3LM67aZyDLR_NJL9OD6mRUWHOQyh3g4j0Pu2gfxG2bqnAUKHjusvzyCt9SgFPr_BvqiVVboKbJT2CnMKE17X9TXh29kBG4pUgxySpMoZiroz9gDZaKkCapAOGs88StJKu4rDYYjwNwCfv5apw7KDW2yUwjsu-8JFscQ3j0KlbepXj_f87G1Cq2LI0JosaCI1xKUmw1csldKhrqcDOmp-hMtRnt6tF_8dQ1ggjBaRUb0qne-6PNV4HeCmgyrGT4o2mjAJDBLu8aaGNuROH0camvmKwRQHg5_L0zQT0I