以下內(nèi)容僅僅作為向大佬們學習中的總結(jié)和記錄
一贾费、名詞解釋
1.什么是image
無論對于系統(tǒng)的動態(tài)鏈接庫還是對于App本身的可執(zhí)行文件而言,他們都算是image(鏡像),而每個App都是以image(鏡像)為單位進行加載的,image包括以下文件:
<1>executable可執(zhí)行文件 比如.o文件
<2>dylib 動態(tài)鏈接庫
<3>bundle 資源文件
2.什么是ImageLoader
image 表示一個二進制文件(可執(zhí)行文件或 so 文件)沥割,里面是被編譯過的符號耗啦、代碼等凿菩,而 ImageLoader 作用是將這些文件加載進內(nèi)存,且每一個文件對應一個ImageLoader實例來負責加載帜讲。
3.Mach-O指哪些文件
Mach是一個操作系統(tǒng)內(nèi)核衅谷,在Mach上,一種可執(zhí)行文件的格式是Mach-O似将。蘋果的大部分文件格式都是Mach-O格式获黔。在iOS中蚀苛,以下文件指的是Mach-O:
<1>Executable 可執(zhí)行文件
<2>Dylib 動態(tài)庫
<3>Bundle
<4>Image
<5>Framework
Mach-o結(jié)構(gòu)如圖:
可以通過命令行otool -l XXX來看看Mach-o內(nèi)部信息
Header頭部 ,包含可以執(zhí)行的CPU架構(gòu)玷氏, 比如Mac的 PPC, PPC64, IA-32, x86-64堵未,iOS的arm系列。
magic盏触,是mach-o文件的魔數(shù)渗蟹,0xfeedface代表的是32位,0xfeedfacf代表64位
cputype和cupsubtype代表的是cpu的類型和其子類型
filetype赞辩,文件類型
ncmds 指的是加載命令(load commands)的數(shù)量
sizeofcmds 表示load commands的總字節(jié)大小
Load commands 加載命令雌芽,包含文件的組織架構(gòu)和在虛擬內(nèi)存中的布局方式。
cmd 是load command的類型,LC_SEGMENT的含義是(將文件中的段映射到進程地址空間)
cmdsize 代表load command的大小
segname 16字節(jié)的段名字辨嗽,當前是__PAGEZERO
vmaddr 段的虛擬內(nèi)存起始地址
vmsize 段的虛擬內(nèi)存大小
.fileoff 段在文件中的偏移量
filesize 段在文件中的大小
maxprot世落,initprot 最高內(nèi)存保護和初始內(nèi)存保護
nsects段中包含section的數(shù)量
Data,可以擁有多個段(segment)糟需,每個段可以擁有零個或多個區(qū)域(section)屉佳。每一個段(segment)都擁有一段虛擬地址映射到進程的地址空間。大部分包含以下三個段:__TEXT 代碼段篮灼,只讀忘古,包括函數(shù),和只讀的字符串
__DATA 數(shù)據(jù)段诅诱,讀寫髓堪,包括可讀寫的全局變量等
__LINKEDIT 包含了方法和變量的元數(shù)據(jù)(位置,偏移量)娘荡,以及代碼簽名等信息干旁。
sectname 第一個是__text ,就是主程序代碼
segname 該section所屬的 segment名
addr 該section在內(nèi)存的啟始位置
size 該section的大小
offset 該section的文件偏移
align 字節(jié)大小對齊
reloff 重定位入口的文件偏移
nreloc 需要重定位的入口數(shù)量
二、啟動過程上
APP啟動分為熱啟動和冷啟動
啟動時間在小于400ms是最佳的炮沐,因為從點擊圖標到顯示Launch Screen争群,到Launch Screen消失這段時間是400ms。啟動時間不可以大于20s大年,否則會被系統(tǒng)殺掉换薄。
t(App總啟動時間) = t1(main()之前的加載時間) + t2(main()之后的加載時間)
t1 =自身App可執(zhí)行文件(.o文件的集合)的加載和系統(tǒng)dylib(動態(tài)鏈接庫)。動態(tài)鏈接庫包括:iOS 中用到的所有系統(tǒng) framework翔试,加載OC runtime方法的libobjc轻要,系統(tǒng)級別的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)垦缅。
具體加載順序是先加載可執(zhí)行文件冲泥,然后加載dyld,是個加載動態(tài)鏈接庫的庫。dyld從可執(zhí)行文件的依賴庫開始加載凡恍,遞歸加載所有的依賴庫
如何加載動態(tài)庫志秃?
在Xcode中,可以通過設置環(huán)境變量來查看App的啟動時間嚼酝,DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS
<1>load dylibs
dyld會首先讀取mach-o文件的Header和load commands浮还。 ?接著就知道了這個可執(zhí)行文件依賴的動態(tài)庫。例如加載動態(tài)庫A到內(nèi)存闽巩,接著檢查A所依賴的動態(tài)庫碑定,就這樣的遞歸加載,直到所有的動態(tài)庫加載完畢又官。通常一個App所依賴的動態(tài)庫在100-400個左右延刘,其中大多數(shù)都是系統(tǒng)的動態(tài)庫,它們會被緩存到dyld shared cache六敬,這樣讀取的效率會很高碘赖。
針對這一步驟的優(yōu)化有:
減少非系統(tǒng)庫的依賴
合并非系統(tǒng)庫
使用靜態(tài)資源,比如把代碼加入主程序
<2>Rebase&Bind
為什么要rebase外构?
有兩種主要的技術來保證應用的安全:ASLR和Code Sign普泡。
ASLR的全稱是Address space layout randomization,翻譯過來就是“地址空間布局隨機化”审编。App被啟動的時候撼班,程序會被映射到邏輯的地址空間(CPU的虛擬地址,從物理地址映射到虛擬地址)垒酬,這個邏輯的地址空間有一個起始地址砰嘁,而ASLR技術使得這個起始地址是隨機的。如果是固定的勘究,那么黑客很容易就可以由起始地址+偏移量找到函數(shù)的地址矮湘。
在進行Code sign的時候,加密不是針對于整個文件口糕,而是針對于每一個Page的缅阳。dyld進行加載的時候,是對每一個page進行獨立的驗證景描。
之所以要rebase是因為剛剛提到的ASLR使得地址隨機化十办,導致起始地址不固定,另外由于Code Sign超棺,導致不能直接修改Image向族。Rebase的時候只需要增加對應的偏移量即可。待Rebase的數(shù)據(jù)都存放在__LINKEDIT中说搅。
bind指向的是鏡像外部的資源指針炸枣。
rebase步驟先進行虏等,需要把鏡像讀入內(nèi)存弄唧,并以page為單位進行加密驗證适肠,保證不會被篡改,所以這一步的瓶頸在IO候引。bind在其后進行侯养,由于要查詢符號表,來指向跨鏡像的資源澄干,加上在rebase階段逛揩,鏡像已被讀入和加密驗證,所以這一步的瓶頸在于CPU計算麸俘。
優(yōu)化該階段的關鍵在于減少__DATA segment中的指針數(shù)量辩稽。我們可以優(yōu)化的點有:
減少Objc類數(shù)量, 減少selector數(shù)量
減少C++虛函數(shù)數(shù)量
轉(zhuǎn)而使用swift stuct(其實本質(zhì)上就是為了減少符號的數(shù)量)
Objc setup
Objective C是動態(tài)語言从媚,所以在執(zhí)行main函數(shù)之前逞泄,需要把類的信息注冊到一個全局的Table中。同時拜效,Objective C支持Category喷众,在初始化的時候,也會把Category中的方法注冊到對應的類中紧憾,同時會唯一Selector到千。
initializers
以上三步屬于靜態(tài)調(diào)整(fix-up),都是在修改__DATA segment中的內(nèi)容赴穗,而這里則開始動態(tài)調(diào)整憔四,開始在堆和棧中寫入內(nèi)容。 在這里的工作有:
Objc的+load()函數(shù)
C/C++靜態(tài)初始化對象和標記為attribute(constructor)的方法
總結(jié)一下:對于main()調(diào)用之前的耗時我們可以優(yōu)化的點有:
減少不必要的framework般眉,因為動態(tài)鏈接比較耗時
check framework應當設為optional和required加矛,如果該framework在當前App支持的所有iOS系統(tǒng)版本都存在,那么就設為required煤篙,否則就設為optional斟览,因為optional會有些額外的檢查
合并或者刪減一些OC類,關于清理項目中沒用到的類辑奈,使用工具AppCode代碼檢查功能苛茂,查到當前項目中沒有用到的類如下:
刪減一些無用的靜態(tài)變量
刪減沒有被調(diào)用到或者已經(jīng)廢棄的方法
將不必須在+load方法中做的事情延遲到+initialize中
盡量不要用C++虛函數(shù)(創(chuàng)建虛函數(shù)表有開銷)
三、啟動過程下
t2 = main方法執(zhí)行之后到AppDelegate類中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法執(zhí)行結(jié)束前這段時間鸠窗。
主要是構(gòu)建第一個界面妓羊,并完成渲染展示。
大部分項目會在該方法里初始化第一個頁面稍计,那么第一個頁面的viewdidload時間就會算t2的時間中
大部分情況下我們都會把界面的初始化過程放在viewDidLoad躁绸,但是這個過程會影響消耗啟動的時間。特別是在類似TabBarController這種會嵌套childViewController的ViewController的情況,它也會把部分children也初始化净刮,因此各種viewDidLoad會遞歸的進行剥哑。
主要優(yōu)化方法
盡量減少初始頁面viewdidload方法中的業(yè)務
四、優(yōu)化總結(jié)
1.移除不需要的動態(tài)庫
2.移除不需要的類
一個叫做fui(Find Unused Imports)的開源項目能很好的分析出不再使用的類.https://github.com/dblock/fui
3.合并功能類似的類和擴展
由于Category和ObjC的動態(tài)綁定有很強的關系淹父,所以實際上分類是比較占用啟動時間的株婴。盡量合并一些分類,會對啟動有一定的優(yōu)化作用暑认。
4.壓縮圖片
5.優(yōu)化applicationdidfinishlaunching
6.優(yōu)化rootviewcontroller加載
參考文章
https://blog.csdn.net/Tencent_Bugly/article/details/77363817?locationNum=1&fps=1
https://blog.csdn.net/u011452278/article/details/54966682