一個(gè) iOS App 的 main 函數(shù)位于 main.m 中妓忍,這是我們熟知的程序入口仇哆。但對(duì) objc 了解更多之后發(fā)現(xiàn),程序在進(jìn)入我們的 main 函數(shù)前已經(jīng)執(zhí)行了很多代碼拷呆,比如熟知的動(dòng)態(tài)庫(kù)的加載, runtime 的初始化, + load 方法等臀稚。本文將跟隨程序執(zhí)行順序,刨根問(wèn)底肋联,從 dyld 到 runtime 威蕉,看看程序從啟動(dòng)到 main 函數(shù)執(zhí)行之前都發(fā)生了什么。
main之前的加載過(guò)程
- Mach-O 加載
- 從 dyld 開始
- 加載程序相關(guān)依賴庫(kù), 并對(duì)這些庫(kù)進(jìn)行鏈接;
- 調(diào)用每個(gè)依賴庫(kù)的初始化方法橄仍,在這一步韧涨,runtime被初始化;
- 將程序依賴的動(dòng)態(tài)鏈接庫(kù)遞歸加載進(jìn)內(nèi)存;
- runtime會(huì)對(duì)項(xiàng)目中所有類進(jìn)行類結(jié)構(gòu)初始化,然后調(diào)用所有的load方法;
- dyld返回main函數(shù)地址沙兰,main函數(shù)被調(diào)用;
- main函數(shù)
Mach-O 加載
關(guān)于 Mach-O 文件這里 有一篇 文章介紹 Mach-O 文件.
Mach-O文件格式是 OS X 與 iOS 系統(tǒng)上的可執(zhí)行文件格式氓奈,像我們編譯過(guò)程產(chǎn)生的 .O 文件翘魄,以及程序的可執(zhí)行文件鼎天,動(dòng)態(tài)庫(kù)等都是Mach-O文件。它的結(jié)構(gòu)如下:
有如下幾個(gè)部分組成:
Header:保存了一些基本信息暑竟,包括了該文件運(yùn)行的平臺(tái)斋射、文件類型、LoadCommands的個(gè)數(shù)等等但荤。
LoadCommands:可以理解為加載命令罗岖,在加載Mach-O文件時(shí)會(huì)使用這里的數(shù)據(jù)來(lái)確定內(nèi)存的分布以及相關(guān)的加載命令。比如我們的main函數(shù)的加載地址腹躁,程序所需的dyld的文件路徑桑包,以及相關(guān)依賴庫(kù)的文件路徑。
Data: 這里包含了具體的代碼纺非、數(shù)據(jù)等等哑了。
系統(tǒng)加載程序可執(zhí)行文件后,通過(guò)分析文件來(lái)獲得dyld所在路徑來(lái)加載dyld烧颖,然后就將后面的事情甩給 dyld 了弱左。
從 dyld 開始
-
dyld: (the dynamic link editor)動(dòng)態(tài)鏈接器,其源碼是開源的炕淮。
一切源于dyldStartup.s
這個(gè)文件拆火,其中用匯編實(shí)現(xiàn)了名為__dyld_start
的方法,匯編太生澀,它主要干了兩件事:
調(diào)用 dyldbootstrap::start()
方法(省去參數(shù))
上個(gè)方法返回了main
函數(shù)地址们镜,填入?yún)?shù)并調(diào)用main
函數(shù)
這個(gè)步驟隨手就能驗(yàn)證出來(lái)币叹,設(shè)置一個(gè)符號(hào)斷點(diǎn)斷在_objc_init:
這個(gè)函數(shù)是runtime的初始化函數(shù),后面會(huì)提到憎账。程序運(yùn)行在很早的時(shí)候斷住套硼,這時(shí)候看調(diào)用棧:
看到了棧底的
dyldbootstrap::start()
方法,繼而調(diào)用了 dyld::_main()
方法胞皱,其中完成了剛才說(shuō)的遞歸加載動(dòng)態(tài)庫(kù)過(guò)程邪意,由于 libSystem
默認(rèn)引入,棧中出現(xiàn)了 libSystem_initializer
的初始化方法反砌。
- ImageLoader: 用于輔助加載特定可執(zhí)行文件格式的類雾鬼,程序中對(duì)應(yīng)實(shí)例可簡(jiǎn)稱為image(如程序可執(zhí)行文件,F(xiàn)ramework庫(kù)宴树,bundle文件)策菜。
兩步走:
- 在程序運(yùn)行時(shí)它先將動(dòng)態(tài)鏈接的 image 遞歸加載 (也就是上面測(cè)試棧中一串的遞歸調(diào)用的時(shí)刻);
- 再?gòu)目蓤?zhí)行文件 image 遞歸加載所有符號(hào);
當(dāng)然所有這些都發(fā)生在我們真正的 main 函數(shù)執(zhí)行前。
-
runtime 與 +load
剛才說(shuō)到libSystem
是若干個(gè)系統(tǒng) lib 的集合酒贬,所以它只是一個(gè)容器 lib 而已又憨,而且它也是開源的,里面實(shí)質(zhì)上就一個(gè)文件锭吨,init.c蠢莺,由libSystem_initializer
逐步調(diào)用到了_objc_init
,這里就是 objc 和 runtime 的初始化入口零如。
除了 runtime 環(huán)境的初始化外躏将,_objc_init
中綁定了新 image 被加載后的 callback:
dyld_register_image_state_change_handler(
dyld_image_state_bound, 1, &map_images);
dyld_register_image_state_change_handler(
dyld_image_state_dependents_initialized, 0, &load_images);
可見 dyld 擔(dān)當(dāng)了 runtime 和 ImageLoader 中間的協(xié)調(diào)者,當(dāng)新 image 加載進(jìn)來(lái)后交由 runtime 去解析這個(gè)二進(jìn)制文件的符號(hào)表和代碼考蕾。繼續(xù)上面的斷點(diǎn)法祸憋,斷住神秘的 +load 函數(shù):清楚的看到整個(gè)調(diào)用棧和順序:
dyld
開始將程序二進(jìn)制文件初始化
交由ImageLoader
讀取image
,其中包含了我們的類肖卧、方法等各種符號(hào)
由于runtime
向dyld
綁定了回調(diào)蚯窥,當(dāng) image
加載到內(nèi)存后,dyld
會(huì)通知 runtime
進(jìn)行處理
runtime
接手后調(diào)用map_images
做解析和處理塞帐,接下來(lái) load_images
中調(diào)用call_load_methods
方法拦赠,遍歷所有加載進(jìn)來(lái)的Class
,按繼承層級(jí)依次調(diào)用Class
的+load
方法和其Category
的 +load
方法
至此壁榕,可執(zhí)行文件中和動(dòng)態(tài)庫(kù)所有的符號(hào)(Class矛紫,Protocol,Selector牌里,IMP颊咬,…)
都已經(jīng)按格式成功加載到內(nèi)存中务甥,被runtime
所管理,再這之后喳篇,runtime
的那些方法(動(dòng)態(tài)添加Class敞临、swizzle
等等才能生效)
main函數(shù)
當(dāng)所有的依賴庫(kù)庫(kù)的 lnitializer 都調(diào)用完后,dyld::main
函數(shù)會(huì)返回程序的 main 函數(shù)地址麸澜,main 函數(shù)被調(diào)用挺尿,從而代碼來(lái)到了我們熟悉的程序入口。
結(jié)語(yǔ)
- 整個(gè)事件由 dyld 主導(dǎo)炊邦,完成運(yùn)行環(huán)境的初始化后编矾,配合 ImageLoader 將二進(jìn)制文件按格式加載到內(nèi)存,
- 動(dòng)態(tài)鏈接依賴庫(kù)馁害,并由 runtime 負(fù)責(zé)加載成 objc 定義的結(jié)構(gòu)窄俏,所有初始化工作結(jié)束后,dyld 調(diào)用真正的 main 函數(shù)碘菜。
這里只是簡(jiǎn)單了概括了從程序啟動(dòng)->dyld加載依賴庫(kù)->runtime初始化->main 的過(guò)程凹蜈。
參考:
iOS 程序 main 函數(shù)之前發(fā)生了什么
iOS程序啟動(dòng)->dyld加載->runtime初始化(初識(shí))