一和泌、應(yīng)用啟動流程
iOS應(yīng)用的啟動可分為pre-main階段和main()階段村缸,其中系統(tǒng)做的事情依次是:
1. pre-main階段
1.1. 加載應(yīng)用的可執(zhí)行文件
1.2. 加載動態(tài)鏈接庫加載器dyld(dynamic loader)
1.3. dyld遞歸加載應(yīng)用所有依賴的dylib(dynamic library 動態(tài)鏈接庫)
2. main()階段
2.1. dyld調(diào)用main()
2.2. 調(diào)用UIApplicationMain()
2.3. 調(diào)用applicationWillFinishLaunching
2.4. 調(diào)用didFinishLaunchingWithOptions
二武氓、應(yīng)用啟動優(yōu)化
1.pre-main階段的優(yōu)化
要對pre-main階段的耗時(shí)做優(yōu)化梯皿,需要根據(jù)dyld加載的過程分為4步:
1.1. Load dylibs
這一階段dyld會分析應(yīng)用依賴的dylib,找到其mach-o文件东羹,打開和讀取這些文件并驗(yàn)證其有效性剂桥,接著會找到代碼簽名注冊到內(nèi)核属提,最后對dylib的每一個(gè)segment調(diào)用mmap()权逗。
一般情況下冤议,iOS應(yīng)用會加載100-400個(gè)dylibs斟薇,其中大部分是系統(tǒng)庫求类,這部分dylib的加載系統(tǒng)已經(jīng)做了優(yōu)化奔垦。
所以尸疆,依賴的dylib越少越好椿猎。在這一步,我們可以做的優(yōu)化有:
- 盡量不使用內(nèi)嵌(embedded)的dylib犯眠,加載內(nèi)嵌dylib性能開銷較大
- 合并已有的dylib和使用靜態(tài)庫(static archives),減少dylib的使用個(gè)數(shù)
- 懶加載dylib症革,但是要注意dlopen()可能造成一些問題,且實(shí)際上懶加載做的工作會更多
1.2. Rebase/Bind
在dylib的加載過程中噪矛,系統(tǒng)為了安全考慮,引入了ASLR(Address Space Layout Randomization)技術(shù)和代碼簽名艇挨。由于ASLR的存在残炮,鏡像(Image缩滨,包括可執(zhí)行文件势就、dylib和bundle)會在隨機(jī)的地址上加載脉漏,和之前指針指向的地址(preferred_address)會有一個(gè)偏差(slide)苞冯,dyld需要修正這個(gè)偏差侧巨,來指向正確的地址舅锄。Rebase在前司忱,Bind在后巧娱,Rebase做的是將鏡像讀入內(nèi)存碉怔,修正鏡像內(nèi)部的指針禁添,性能消耗主要在IO撮胧。Bind做的是查詢符號表老翘,設(shè)置指向鏡像外部的指針芹啥,性能消耗主要在CPU計(jì)算铺峭。
所以墓怀,指針數(shù)量越少越好卫键。在這一步傀履,我們可以做的優(yōu)化有:
- 減少ObjC類(class)、方法(selector)钓账、分類(category)的數(shù)量
- 減少C++虛函數(shù)的的數(shù)量(創(chuàng)建虛函數(shù)表有開銷)
- 使用Swift structs(內(nèi)部做了優(yōu)化,符號數(shù)量更少)
1.3. Objc setup
大部分ObjC初始化工作已經(jīng)在Rebase/Bind階段做完了絮宁,這一步dyld會注冊所有聲明過的ObjC類,將分類插入到類的方法列表里绍昂,再檢查每個(gè)selector的唯一性。
在這一步倒沒什么優(yōu)化可做的窘游,Rebase/Bind階段優(yōu)化好了唠椭,這一步的耗時(shí)也會減少忍饰。
1.4. 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)化有:
- 少在類的+load方法里做事情俱两,盡量把這些事情推遲到+initiailize
- 減少構(gòu)造器函數(shù)個(gè)數(shù),在構(gòu)造器函數(shù)里少做些事情
- 減少C++靜態(tài)全局變量的個(gè)數(shù)
2.main()階段的優(yōu)化
這一階段的優(yōu)化主要是減少didFinishLaunchingWithOptions方法里的工作宪彩,在didFinishLaunchingWithOptions方法里休讳,我們會創(chuàng)建應(yīng)用的window尿孔,指定其rootViewController俊柔,調(diào)用window的makeKeyAndVisible方法讓其可見活合。
由于業(yè)務(wù)需要雏婶,我們會初始化各個(gè)三方庫白指,設(shè)置系統(tǒng)UI風(fēng)格留晚,檢查是否需要顯示引導(dǎo)頁告嘲、是否需要登錄错维、是否有新版本等,由于歷史原因需五,這里的代碼容易變得比較龐大,啟動耗時(shí)難以控制轧坎。
所以,滿足業(yè)務(wù)需要的前提下缸血,didFinishLaunchingWithOptions在主線程里做的事情越少越好。在這一步捎泻,我們可以做的優(yōu)化有:
- 梳理各個(gè)三方庫飒炎,找到可以延遲加載的庫笆豁,做延遲加載處理郎汪,比如放到首頁控制器的viewDidAppear方法里闯狱。
- 梳理業(yè)務(wù)邏輯煞赢,把可以延遲執(zhí)行的邏輯,做延遲執(zhí)行處理照筑。比如檢查新版本、注冊推送通知等邏輯。
- 避免復(fù)雜/多余的計(jì)算波俄。
- 避免在首頁控制器的viewDidLoad和viewWillAppear做太多事情,這2個(gè)方法執(zhí)行完蛾默,首頁控制器才能顯示,部分可以延遲創(chuàng)建的視圖應(yīng)做延遲創(chuàng)建/懶加載處理趴生。
- 采用性能更好的API阀趴。
- 首頁控制器用純代碼方式來構(gòu)建苍匆。