main()調(diào)用之前的加載過程
App開始啟動后,系統(tǒng)首先加載可執(zhí)行文件(自身App的所有.o文件的集合),然后加載動態(tài)鏈接庫dyld。
dyld是一個專門用來加載動態(tài)鏈接庫的庫。
dyld源碼鏈接
執(zhí)行從dyld開始,dyld從可執(zhí)行文件的依賴開始, 遞歸加載所有的依賴動態(tài)鏈接庫。
動態(tài)鏈接庫包括:
-
iOS中用到的所有系統(tǒng)framework, - 加載
OC runtime方法的libobjc, - 系統(tǒng)級別的
libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。
其實無論對于系統(tǒng)的動態(tài)鏈接庫還是對于App本身的可執(zhí)行文件而言,他們都算是image(鏡像),而每個App都是以image(鏡像)為單位進(jìn)行加載的。
什么是image(鏡像)
1.executable可執(zhí)行文件 比如.o文件。
2.dylib 動態(tài)鏈接庫framework就是動態(tài)鏈接庫和相應(yīng)資源包含在一起的一個文件夾結(jié)構(gòu)。
3.bundle 資源文件 只能用dlopen加載,不推薦使用這種方式加載。
除了我們App本身的可行性文件,系統(tǒng)中所有的framework比如UIKit、Foundation等都是以動態(tài)鏈接庫的方式集成進(jìn)App中的。
系統(tǒng)使用動態(tài)鏈接有幾點好處:
代碼共用:很多程序都動態(tài)鏈接了這些 lib,但它們在內(nèi)存和磁盤中中只有一份。 易于維護(hù):由于被依賴的 lib 是程序執(zhí)行時才鏈接的,所以這些 lib 很容易做更新,比如libSystem.dylib 是 libSystem.B.dylib 的替身,哪天想升級直接換成libSystem.C.dylib 然后再替換替身就行了。 減少可執(zhí)行文件體積:相比靜態(tài)鏈接,動態(tài)鏈接在編譯時不需要打進(jìn)去,所以可執(zhí)行文件的體積要小很多。

如上圖所示,不同進(jìn)程之間共用系統(tǒng)dylib的_TEXT區(qū),但是各自維護(hù)對應(yīng)的_DATA區(qū)。
所有動態(tài)鏈接庫和我們App中的靜態(tài)庫.a和所有類文件編譯后的.o文件最終都是由dyld(the dynamic link editor),Apple的動態(tài)鏈接器來加載到內(nèi)存中。每個image都是由一個叫做ImageLoader的類來負(fù)責(zé)加載(一一對應(yīng)),那么ImageLoader又是什么呢?
什么是ImageLoader
image 表示一個二進(jìn)制文件(可執(zhí)行文件或 so 文件),里面是被編譯過的符號、代碼等,所以 ImageLoader 作用是將這些文件加載進(jìn)內(nèi)存,且每一個文件對應(yīng)一個ImageLoader實例來負(fù)責(zé)加載。
兩步走: 在程序運行時它先將動態(tài)鏈接的 image 遞歸加載 (也就是上面測試棧中一串的遞歸調(diào)用的時刻)。 再從可執(zhí)行文件 image 遞歸加載所有符號。
當(dāng)然所有這些都發(fā)生在我們真正的main函數(shù)執(zhí)行前。
動態(tài)鏈接庫加載的具體流程
動態(tài)鏈接庫的加載步驟具體分為5步:
1.load dylibs image 讀取庫鏡像文件
2.Rebase image
3.Bind image
4.Objc setup
5.initializers
1.load dylibs image
在每個動態(tài)庫的加載過程中, dyld需要:
1.分析所依賴的動態(tài)庫
2.找到動態(tài)庫的mach-o文件
3.打開文件
4.驗證文件
5.在系統(tǒng)核心注冊文件簽名
6.對動態(tài)庫的每一個segment調(diào)用mmap()
通常的,一個App需要加載100到400個dylibs, 但是其中的系統(tǒng)庫被優(yōu)化,可以很快的加載。
針對這一步驟的優(yōu)化有:
1.減少非系統(tǒng)庫的依賴
2.合并非系統(tǒng)庫
3.使用靜態(tài)資源,比如把代碼加入主程序
2.rebase/bind
由于ASLR(address space layout randomization)的存在,可執(zhí)行文件和動態(tài)鏈接庫在虛擬內(nèi)存中的加載地址每次啟動都不固定,所以需要這2步來修復(fù)鏡像中的資源指針,來指向正確的地址。
rebase修復(fù)的是指向當(dāng)前鏡像內(nèi)部的資源指針;
而bind指向的是鏡像外部的資源指針。
rebase步驟先進(jìn)行,需要把鏡像讀入內(nèi)存,并以page為單位進(jìn)行加密驗證,保證不會被篡改,所以這一步的瓶頸在IO。
bind在其后進(jìn)行,由于要查詢符號表,來指向跨鏡像的資源,加上在rebase階段,鏡像已被讀入和加密驗證,所以這一步的瓶頸在于CPU計算。
//通過命令行可以查看相關(guān)的資源指針:
xcrun dyldinfo -rebase -bind -lazy_bind myApp.App/myApp
優(yōu)化該階段的關(guān)鍵在于減少__DATA segment中的指針數(shù)量。
可以優(yōu)化的點有:
1.減少Objc類數(shù)量, 減少selector數(shù)量
2.減少C++虛函數(shù)數(shù)量
3.轉(zhuǎn)而使用swift stuct(其實本質(zhì)上就是為了減少符號的數(shù)量)
3.Objc setup
這一步主要工作是:
1.注冊Objc類 (class registration)
2.把category的定義插入方法列表 (category registration)
3.保證每一個selector唯一 (selctor uniquing)
4.由于之前2步驟的優(yōu)化,這一步實際上沒有什么可做的。
4.initializers
以上三步屬于靜態(tài)調(diào)整(fix-up),都是在修改__DATA segment中的內(nèi)容,而這里則開始動態(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ù)中有繁重的工作,那么會拖慢啟動速度
Objc的load函數(shù)和C++的靜態(tài)構(gòu)造函數(shù)采用由底向上的方式執(zhí)行,來保證每個執(zhí)行的方法,都可以找到所依賴的動態(tài)庫。
1).dyld 開始將程序二進(jìn)制文件初始化
2).交由 ImageLoader 讀取 image,其中包含了我們的類、方法等各種符號
3).由于 runtime 向 dyld 綁定了回調(diào),當(dāng) image 加載到內(nèi)存后,dyld 會通知 runtime 進(jìn)行處理
4).runtime 接手后調(diào)用 mapimages 做解析和處理,接下來 loadimages 中調(diào)用 callloadmethods 方法,遍歷所有加載進(jìn)來的 Class,按繼承層級依次調(diào)用 Class 的 +load 方法和其 Category 的 +load 方法
至此
至此,可執(zhí)行文件中和動態(tài)庫所有的符號(Class,Protocol,Selector,IMP,…)都已經(jīng)按格式成功加載到內(nèi)存中,被 runtime 所管理,再這之后,runtime 的那些方法(動態(tài)添加 Class、swizzle 等等才能生效)。
整個事件由 dyld 主導(dǎo),完成運行環(huán)境的初始化后,配合 ImageLoader 將二進(jìn)制文件按格式加載到內(nèi)存, 動態(tài)鏈接依賴庫,并由 runtime 負(fù)責(zé)加載成 objc 定義的結(jié)構(gòu),所有初始化工作結(jié)束后,dyld 調(diào)用真正的 main 函數(shù)。
結(jié)語
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家具有一定的參考學(xué)習(xí)價值,同時歡迎大家進(jìn)入小編iOS交流圈:937194184,一起交流學(xué)習(xí),謝謝大家的支持。