一击碗、冷啟動和熱啟動
- 定義:
- 1.關(guān)于冷啟動:業(yè)界對冷啟動的定義沒有問題,普遍認為是手機開機后第一次啟動某個APP李茫。
- 2.關(guān)于熱啟動:
對熱啟動有兩種不同的看法:- 1.有些人認為是按下home鍵把APP掛到后臺菠劝,之后點擊APP的icon再拉回來到前臺算是熱啟動;
- 2.也有些人認為是手機開機后在短時間內(nèi)第二次啟動APP(殺掉進程重啟)算是熱啟動(此時dyld會對部分APP的數(shù)據(jù)和庫進行緩存派任,所以比第一次啟動要快)。
(一般認為APP從后臺拉起到前臺的時間沒啥研究的意義璧南,所以在統(tǒng)計啟動時間時掌逛,會傾向于后一種說法,不過具體怎么定義看個人司倚,知道其中的區(qū)別就好)
如果啟動過豆混,內(nèi)存頁就已經(jīng)加載進內(nèi)存了,內(nèi)存頁如果要銷毀只能是被覆蓋动知,所以這時候的加載進物理內(nèi)存的時候皿伺,實際內(nèi)存中已經(jīng)存在值,所以啟動就會變快盒粮。
二心傀、優(yōu)化的方向
1.減少流程的數(shù)量。
2.減少每個流程的消耗拆讯。
三脂男、APP的啟動過程
四、啟動時間分布
在Xcode中种呐,可以通過設(shè)置環(huán)境變量來查看App的啟動時間宰翅,DYLD_PRINT_STATISTICS
和DYLD_PRINT_STATISTICS_DETAILS
。
根據(jù)xcode打印可以看到:
動態(tài)庫的加載
-
rebase/binding
- 方法爽室、類汁讼、Category這些需要rebase和 binding
- binding
Mach-O _DATA建立指針,指向外部函數(shù)阔墩,我們編譯的時候嘿架,共享緩存庫里邊的代碼指向的就是這里,鏈接的時候啸箫,通過dyld指向外部具體真實的地址耸彪。PIC技術(shù)
fishook就是利用這個中轉(zhuǎn),hook的系統(tǒng)c函數(shù)忘苛。
-
Objc setup
- 類的初始化過程在這里邊
①讀取二進制文件的 DATA 段內(nèi)容蝉娜,找到與 objc 相關(guān)的信息
②注冊 Objc 類,ObjC Runtime 需要維護一張映射類名與類的全局表扎唾。當加載一個 dylib 時召川,其定義的所有的類都需要被注冊到這個全局表中;
③讀取 protocol 以及 category 的信息胸遇,把category的定義插入方法列表 (category registration)荧呐,
④確保 selector 的唯一性
- 類的初始化過程在這里邊
initializer
load等函數(shù),C++的構(gòu)造函數(shù)
耗時比較多的
加載自己的庫可能比較耗時
兩個階段分別是 pre- main 和 main啟動到viewDidAppear:
pre-main過程:
什么是dyld?
dyld是一個專門用來加載動態(tài)鏈接庫的庫纸镊。
執(zhí)行從dyld開始倍阐,dyld從可執(zhí)行文件的依賴開始, 遞歸加載所有的依賴動態(tài)鏈接庫。
五薄腻、 Dyld加載過程
加載Mach-O文件收捣,加載一塊比較大的內(nèi)存空間,地址重定向
啟動dyld的main函數(shù)
- 配置xcode的環(huán)境變量 (arguments)
- 加載共享緩存庫庵楷,UIKit 和 Foundation這些
加載LoadCommons段一系列加載
加載動態(tài)庫罢艾,包括自己的和系統(tǒng)的,實例化主程序的imageLoader尽纽,添加進imageList
鏈接主程序
開始初始化主程序
*runtime配合加載咐蚯,runtime加載loadImages方法,這個方法就是prepare_load_methods(mh)
*加載load和C++構(gòu)造函數(shù)
找main函數(shù)
配置(環(huán)境變量) — 加載(共享動態(tài)庫弄贿,主程序春锋,動態(tài)庫) — 鏈接(鏈接主程序,動態(tài)庫) — 初始化(程序)
- 加載動態(tài)庫過程差凹,動態(tài)鏈接器dyld需要做如下操作
1.分析依賴的動態(tài)庫
2.找到動態(tài)庫的Mach-O文件
3.打開文件
4.驗證文件
5.在系統(tǒng)的內(nèi)核注冊文件簽名
6.對動態(tài)庫的每個segment調(diào)用mmap()
針對這一步優(yōu)化
1.減少非系統(tǒng)的動態(tài)庫
2.使用靜態(tài)庫而不是動態(tài)庫
3.合并非系統(tǒng)的動態(tài)庫為一個
1.動態(tài)庫盡可能少期奔,不要多與6個侧馅,如果多了,盡可能合并
2.20000個類 800毫秒
3.swift靜態(tài)語言呐萌,性能更高
六馁痴、ObjC SetUp
由于之前2步驟的優(yōu)化,這一步實際上沒有什么可做的肺孤。幾乎都靠 Rebasing 和 Binding 步驟中減少所需 fix-up 內(nèi)容罗晕。因為前面的工作也會使得這步耗時減少。
七赠堵、Initializers
- 以上三步屬于靜態(tài)調(diào)整小渊,都是在修改
__DATA segment
中的內(nèi)容
現(xiàn)在開始動態(tài)調(diào)整,在堆和棧中寫入內(nèi)容茫叭。 工作主要有:
1酬屉、Objc
的+load()
函數(shù)
2、C++的構(gòu)造函數(shù)屬性函數(shù) 形如attribute((constructor)) void DoSomeInitializationWork()
3杂靶、非基本類型的C++靜態(tài)全局變量的創(chuàng)建(通常是類或結(jié)構(gòu)體)(non-trivial initializer) 比如一個全局靜態(tài)結(jié)構(gòu)體的構(gòu)建梆惯,如果在構(gòu)造函數(shù)中有繁重的工作,那么會拖慢啟動速度
可以做的優(yōu)化有:
①使用 +initialize 來替代 +load
②不要使用 atribute((constructor)) 將方法顯式標記為初始化器吗垮,而是讓初始化方法調(diào)用時才執(zhí)行垛吗。 比如使用 dispatch_once(),pthread_once() 或 std::once()。也就是在第一次使用時才初始化烁登,推遲了一部分工作耗時怯屉。 也盡量不要用到C++的靜態(tài)對象。
從效率上來說饵沧,在
+load
和+initialize
里執(zhí)行同樣的代碼锨络,效率是一樣的,即使有差距狼牺,也不會差距太大羡儿。 但所有的+load
方法都在啟動的時候調(diào)用,方法多了就會嚴重影響啟動速度了是钥。 就說我們項目中掠归,有200個左右+load
方法,一共耗時大概1s 左右悄泥,這塊就會嚴重影響到用戶感知了虏冻。 而+initialize
方法是在對應(yīng) Class 第一次使用的時候調(diào)用,這是一個懶加載的方法弹囚,理想情況下厨相,這200個+load
方法都使用+initialize
來代替,將耗時分攤到用戶使用過程中,每個方法平均耗時只有5ms蛮穿,用戶完全可以無感知庶骄。
八、二進制重排
iOS的虛擬內(nèi)存和物理內(nèi)存:
之前所有應(yīng)用都加載進內(nèi)存條中践磅,會有安全問題瓢姻,會訪問到其他人的軟件中去
iOS虛擬內(nèi)存有4G的
CPU通過 操作系統(tǒng)映射表 映射虛擬內(nèi)存和物理真實內(nèi)存
也就是我們訪問虛擬內(nèi)存,都會走到映射表音诈,然后映射到真實的物理內(nèi)存。(每個映虛擬內(nèi)存對應(yīng)一個映射表)
內(nèi)存是以字節(jié)為單位细溅,內(nèi)存以頁管理 PAGE,Linux每1頁4KB儡嘶,iOS每一頁16KB
用這種分頁加載喇聊,用到哪塊內(nèi)存就加載到真實內(nèi)存
上述P2加載的時候,發(fā)現(xiàn)真實內(nèi)存沒有蹦狂,缺頁誓篱,CPU會臨時加載
如果啟動的時候缺頁比較多(缺頁異常),就會不斷去映射到物理內(nèi)存凯楔,就會卡頓
iOS的內(nèi)存地址窜骄,與編譯順序有關(guān),所以如果方法在不同的類摆屯,類的加載差距比較大邻遏,那么就會加載比較多的頁,就會浪費時間虐骑。
iOS系統(tǒng)還會對加載的每一頁做簽名認證准验,所以更加消耗時間。
system trace
Main Thread
Virtual Memory File Backard Page In
ldyd
order_file 自己寫一個文件廷没,里邊指定方法的順序糊饱,可以將啟動要調(diào)用的方法,都放到里邊颠黎。
libc-order在objc源碼里邊另锋,這就是二進制重排
所有OC方法獲取
用fishhook 去 HOOK objc_msgSend()方法,可以拿到所有的函數(shù)
可以用Clang插庒的方式AOP
八盏缤、main過程:
- Main函數(shù)之后
1.懶加載
2.發(fā)揮CPU的價值砰蠢,用多線程的方式。
3.框架的搭建盡量避免stroyboard 和 xib
分階段加載:
-
在didFinishLunch中啟動的:
- 埋點功能唉铜、
- Crash 采集
- 網(wǎng)絡(luò)配置等
-
在首頁viewDidappear
- 初始化SDK台舱,友盟SDK、人臉識別SDK后移
- 開始定位(顯示定位中),定位可以緩存竞惋,先加載柜去,定位同步進行,如果緩存的地理位置不一樣拆宛,然后才更新
- 配置下發(fā)等
多線程啟動
嘗試使用多線程啟動
將數(shù)據(jù)庫的配置嗓奢、SDwebimage UA的配置放到子線程中初始化,引入狀態(tài)管理浑厚,需要監(jiān)控子線程任務(wù)狀態(tài)股耽,判斷是否取消假的開屏頁面。
廣告同步加載钳幅,2秒回來了就展示物蝙,不回來就過
Timer Profile的使用