main()調用之前的加載過程
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(鏡像)為單位進行加載的。
什么是image(鏡像)
1.executable可執(zhí)行文件 比如.o文件姨蟋。
2.dylib 動態(tài)鏈接庫 framework就是動態(tài)鏈接庫和相應資源包含在一起的一個文件夾結構屉凯。
3.bundle 資源文件 只能用dlopen加載,不推薦使用這種方式加載眼溶。
除了我們App本身的可行性文件悠砚,系統(tǒng)中所有的framework比如UIKit、Foundation等都是以動態(tài)鏈接庫的方式集成進App中的堂飞。
系統(tǒng)使用動態(tài)鏈接有幾點好處:
代碼共用:很多程序都動態(tài)鏈接了這些 lib灌旧,但它們在內存和磁盤中中只有一份。 易于維護:由于被依賴的 lib 是程序執(zhí)行時才鏈接的绰筛,所以這些 lib 很容易做更新枢泰,比如libSystem.dylib 是 libSystem.B.dylib 的替身,哪天想升級直接換成libSystem.C.dylib 然后再替換替身就行了铝噩。 減少可執(zhí)行文件體積:相比靜態(tài)鏈接衡蚂,動態(tài)鏈接在編譯時不需要打進去,所以可執(zhí)行文件的體積要小很多骏庸。
如上圖所示毛甲,不同進程之間共用系統(tǒng)dylib的_TEXT區(qū),但是各自維護對應的_DATA區(qū)具被。
所有動態(tài)鏈接庫和我們App中的靜態(tài)庫.a和所有類文件編譯后的.o文件最終都是由dyld(the dynamic link editor)玻募,Apple的動態(tài)鏈接器來加載到內存中。每個image都是由一個叫做ImageLoader的類來負責加載(一一對應)硬猫,那么ImageLoader又是什么呢补箍?
什么是ImageLoader
image 表示一個二進制文件(可執(zhí)行文件或 so 文件),里面是被編譯過的符號啸蜜、代碼等坑雅,所以 ImageLoader 作用是將這些文件加載進內存,且每一個文件對應一個ImageLoader實例來負責加載衬横。
兩步走: 在程序運行時它先將動態(tài)鏈接的 image 遞歸加載 (也就是上面測試棧中一串的遞歸調用的時刻)裹粤。 再從可執(zhí)行文件 image 遞歸加載所有符號。
當然所有這些都發(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調用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)鏈接庫在虛擬內存中的加載地址每次啟動都不固定债朵,所以需要這2步來修復鏡像中的資源指針,來指向正確的地址瀑凝。
rebase修復的是指向當前鏡像內部的資源指針序芦;
而bind指向的是鏡像外部的資源指針。
rebase步驟先進行粤咪,需要把鏡像讀入內存谚中,并以page為單位進行加密驗證,保證不會被篡改寥枝,所以這一步的瓶頸在IO宪塔。
bind在其后進行,由于要查詢符號表脉顿,來指向跨鏡像的資源蝌麸,加上在rebase階段,鏡像已被讀入和加密驗證艾疟,所以這一步的瓶頸在于CPU計算。
//通過命令行可以查看相關的資源指針:
xcrun dyldinfo -rebase -bind -lazy_bind myApp.App/myApp
優(yōu)化該階段的關鍵在于減少__DATA segment中的指針數(shù)量敢辩。
可以優(yōu)化的點有:
1.減少Objc類數(shù)量蔽莱, 減少selector數(shù)量
2.減少C++虛函數(shù)數(shù)量
3.轉而使用swift stuct(其實本質上就是為了減少符號的數(shù)量)
3.Objc setup
這一步主要工作是:
1.注冊Objc類 (class registration)
2.把category的定義插入方法列表 (category registration)
3.保證每一個selector唯一 (selctor uniquing)
4.由于之前2步驟的優(yōu)化,這一步實際上沒有什么可做的戚长。
4.initializers
以上三步屬于靜態(tài)調整(fix-up)盗冷,都是在修改__DATA segment中的內容,而這里則開始動態(tài)調整同廉,開始在堆和堆棧中寫入內容仪糖。 在這里的工作有:
1.Objc的+load()函數(shù)
2.C++的構造函數(shù)屬性函數(shù) 形如attribute((constructor)) void DoSomeInitializationWork()
3.非基本類型的C++靜態(tài)全局變量的創(chuàng)建(通常是類或結構體)(non-trivial initializer) 比如一個全局靜態(tài)結構體的構建,如果在構造函數(shù)中有繁重的工作迫肖,那么會拖慢啟動速度
Objc的load函數(shù)和C++的靜態(tài)構造函數(shù)采用由底向上的方式執(zhí)行锅劝,來保證每個執(zhí)行的方法,都可以找到所依賴的動態(tài)庫蟆湖。
1).dyld 開始將程序二進制文件初始化
2).交由 ImageLoader 讀取 image故爵,其中包含了我們的類、方法等各種符號
3).由于 runtime 向 dyld 綁定了回調隅津,當 image 加載到內存后诬垂,dyld 會通知 runtime 進行處理
4).runtime 接手后調用 mapimages 做解析和處理,接下來 loadimages 中調用 callloadmethods 方法伦仍,遍歷所有加載進來的 Class结窘,按繼承層級依次調用 Class 的 +load 方法和其 Category 的 +load 方法
至此
至此,可執(zhí)行文件中和動態(tài)庫所有的符號(Class充蓝,Protocol隧枫,Selector,IMP,…)都已經(jīng)按格式成功加載到內存中悠垛,被 runtime 所管理线定,再這之后,runtime 的那些方法(動態(tài)添加 Class确买、swizzle 等等才能生效)斤讥。
整個事件由 dyld 主導,完成運行環(huán)境的初始化后湾趾,配合 ImageLoader 將二進制文件按格式加載到內存芭商, 動態(tài)鏈接依賴庫,并由 runtime 負責加載成 objc 定義的結構搀缠,所有初始化工作結束后铛楣,dyld 調用真正的 main 函數(shù)。