dyld加載流程

簡(jiǎn)介

dyld(the dynamic link editor)是蘋(píng)果的動(dòng)態(tài)鏈接器繁调,是蘋(píng)果操作系統(tǒng)一個(gè)重要組成部分弓千,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后书闸,交由dyld負(fù)責(zé)余下的工作。而且它是開(kāi)源的躲惰,任何人可以通過(guò)蘋(píng)果官網(wǎng)下載它的源碼來(lái)閱讀理解它的運(yùn)作方式,了解系統(tǒng)加載動(dòng)態(tài)庫(kù)的細(xì)節(jié)智袭。

編譯過(guò)程

源文件:載入.h奔缠、.m、.cpp等文件
預(yù)處理:替換宏吼野,刪除注釋校哎,展開(kāi)頭文件,產(chǎn)生.i文件
編譯:將.i文件轉(zhuǎn)換為匯編語(yǔ)言瞳步,產(chǎn)生.s文件
匯編:將匯編文件轉(zhuǎn)換為機(jī)器碼文件闷哆,產(chǎn)生.o文件
鏈接:對(duì).o文件中引用其他庫(kù)的地方進(jìn)行引用,生成最后的可執(zhí)行文件

編譯過(guò)程.png

dyld加載流程分析

dyld::_main函數(shù)源碼分析

  • 進(jìn)入dyld::_main的源碼實(shí)現(xiàn)单起,特別長(zhǎng)抱怔,大約600多行,如果對(duì)dyld加載流程不太了解的童鞋嘀倒,可以根據(jù)_main函數(shù)的返回值進(jìn)行反推屈留,這里就多作說(shuō)明。在_main函數(shù)中主要做了一下幾件事情:

    • 【第一步:環(huán)境變量配置】:根據(jù)環(huán)境變量設(shè)置相應(yīng)的值以及獲取當(dāng)前運(yùn)行架構(gòu)

      image
    • 【第二步:共享緩存】:檢查是否開(kāi)啟了共享緩存测蘑,以及共享緩存是否映射到共享區(qū)域灌危,例如UIKitCoreFoundation

      image
    • 【第三步:主程序的初始化】:調(diào)用instantiateFromLoadedImage函數(shù)實(shí)例化了一個(gè)ImageLoader對(duì)象

      image
    • 【第四步:插入動(dòng)態(tài)庫(kù)】:遍歷DYLD_INSERT_LIBRARIES環(huán)境變量碳胳,調(diào)用loadInsertedDylib加載

      image
    • 【第五步:link 主程序

      image
    • 【第六步:link 動(dòng)態(tài)庫(kù)

      image
    • 【第七步:弱符號(hào)綁定

      image
    • 【第八步:執(zhí)行初始化方法

      image
    • 【第九步:尋找主程序入口main函數(shù)】:從Load Command讀取LC_MAIN入口勇蝙,如果沒(méi)有,就讀取LC_UNIXTHREAD挨约,這樣就來(lái)到了日常開(kāi)發(fā)中熟悉的main函數(shù)了

      image

第八步:執(zhí)行初始化方法

  • 進(jìn)入initializeMainExecutable源碼,主要是循環(huán)遍歷味混,都會(huì)執(zhí)行runInitializers方法

    image
  • 全局搜索runInitializers(cons,找到如下源碼,其核心代碼是processInitializers函數(shù)的調(diào)用

    image
  • 進(jìn)入processInitializers函數(shù)的源碼實(shí)現(xiàn)烫罩,其中對(duì)鏡像列表調(diào)用recursiveInitialization函數(shù)進(jìn)行遞歸實(shí)例化

    image
  • 全局搜索recursiveInitialization(cons函數(shù),其源碼實(shí)現(xiàn)如下

    image

在這里惜傲,需要分成兩部分探索,一部分是notifySingle函數(shù)贝攒,一部分是doInitialization函數(shù),首先探索notifySingle函數(shù)

notifySingle 函數(shù)
  • 全局搜索notifySingle(函數(shù),其重點(diǎn)是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());這句

    image
  • 全局搜索sNotifyObjCInit时甚,發(fā)現(xiàn)沒(méi)有找到實(shí)現(xiàn)隘弊,有賦值操作

    image
  • 搜索registerObjCNotifiers在哪里調(diào)用了,發(fā)現(xiàn)在_dyld_objc_notify_register進(jìn)行了調(diào)用

    image

    注意:_dyld_objc_notify_register的函數(shù)需要在libobjc源碼中搜索

  • objc4-781源碼中搜索_dyld_objc_notify_register荒适,發(fā)現(xiàn)在_objc_init源碼中調(diào)用了該方法梨熙,并傳入了參數(shù),所以sNotifyObjCInit賦值的就是objc中的load_images刀诬,而load_images會(huì)調(diào)用所有的+load方法咽扇。所以綜上所述,notifySingle是一個(gè)回調(diào)函數(shù)

    image

load函數(shù)加載

下面我們進(jìn)入load_images的源碼看看其實(shí)現(xiàn),以此來(lái)證明load_images中調(diào)用了所有的load函數(shù)

  • 通過(guò)objc源碼中_objc_init源碼實(shí)現(xiàn)质欲,進(jìn)入load_images的源碼實(shí)現(xiàn)

    image
  • 進(jìn)入call_load_methods源碼實(shí)現(xiàn)树埠,可以發(fā)現(xiàn)其核心是通過(guò)do-while循環(huán)調(diào)用+load方法

    image
  • 進(jìn)入call_class_loads源碼實(shí)現(xiàn),了解到這里調(diào)用的load方法證實(shí)我們前文提及的類的load方法

    image

所以嘶伟,load_images調(diào)用了所有的load函數(shù)怎憋,以上的源碼分析過(guò)程正好對(duì)應(yīng)堆棧的打印信息

image

【總結(jié)】load的源碼鏈為:_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> dyld::notifySingle(是一個(gè)回調(diào)處理) --> sNotifyObjCInit --> load_images(libobjc.A.dylib)

那么問(wèn)題又來(lái)了,_objc_init是什么時(shí)候調(diào)用的呢九昧?請(qǐng)接著往下看

doInitialization 函數(shù)
  • 走到objc_objc_init函數(shù)绊袋,發(fā)現(xiàn)走不通了,我們回退到recursiveInitialization遞歸函數(shù)的源碼實(shí)現(xiàn)铸鹰,發(fā)現(xiàn)我們忽略了一個(gè)函數(shù)doInitialization

    image
  • 進(jìn)入doInitialization函數(shù)的源碼實(shí)現(xiàn)

    image

    這里也需要分成兩部分癌别,一部分是doImageInit函數(shù),一部分是doModInitFunctions函數(shù)

    • 進(jìn)入doImageInit源碼實(shí)現(xiàn)蹋笼,其核心主要是for循環(huán)加載方法的調(diào)用规个,這里需要注意的一點(diǎn)是,libSystem的初始化必須先運(yùn)行

      image
    • 進(jìn)入doModInitFunctions源碼實(shí)現(xiàn)姓建,這個(gè)方法中加載了所有Cxx文件

      image

      可以通過(guò)測(cè)試程序的堆棧信息來(lái)驗(yàn)證,在C++方法處加一個(gè)斷點(diǎn)

      image

走到這里诞仓,還是沒(méi)有找到_objc_init的調(diào)用?怎么辦呢速兔?放棄嗎墅拭?當(dāng)然不行,我們還可以通過(guò)_objc_init加一個(gè)符號(hào)斷點(diǎn)來(lái)查看調(diào)用_objc_init前的堆棧信息涣狗,

  • _objc_init加一個(gè)符號(hào)斷點(diǎn)谍婉,運(yùn)行程序,查看_objc_init斷住后的堆棧信息

    image
  • libsystem中查找libSystem_initializer镀钓,查看其中的實(shí)現(xiàn)

    image
  • 根據(jù)前面的堆棧信息穗熬,我們發(fā)現(xiàn)走的是libSystem_initializer中會(huì)調(diào)用libdispatch_init函數(shù),而這個(gè)函數(shù)的源碼是在libdispatch開(kāi)源庫(kù)中的丁溅,在libdispatch中搜索libdispatch_init

    image
  • 進(jìn)入_os_object_init源碼實(shí)現(xiàn)唤蔗,其源碼實(shí)現(xiàn)調(diào)用了_objc_init函數(shù)

    image

    結(jié)合上面的分析,從初始化_objc_init注冊(cè)的_dyld_objc_notify_register的參數(shù)2窟赏,即load_images妓柜,到sNotifySingle --> sNotifyObjCInie=參數(shù)2sNotifyObjcInit()調(diào)用,形成了一個(gè)閉環(huán)

所以可以簡(jiǎn)單的理解為sNotifySingle這里是添加通知即addObserver涯穷,_objc_init中調(diào)用_dyld_objc_notify_register相當(dāng)于發(fā)送通知棍掐,即push藏姐,而sNotifyObjcInit相當(dāng)于通知的處理函數(shù)氓扛,即selector

【總結(jié)】:_objc_init的源碼鏈:_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> doInitialization -->libSystem_initializer(libSystem.B.dylib) --> _os_object_init(libdispatch.dylib) --> _objc_init(libobjc.A.dylib)

dyld加載流程(轉(zhuǎn)載字Style_月月).png

補(bǔ)充

靜態(tài)庫(kù)動(dòng)態(tài)庫(kù)

靜態(tài)庫(kù):在鏈接階段,會(huì)將可匯編生成的目標(biāo)程序與引用的庫(kù)一起鏈接打包到可執(zhí)行文件當(dāng)中泡孩。此時(shí)的靜態(tài)庫(kù)就不會(huì)在改變了,因?yàn)樗蔷幾g時(shí)被直接拷貝一份粟誓,復(fù)制到目標(biāo)程序里的
好處:編譯完成后奏寨,庫(kù)文件實(shí)際上就沒(méi)有作用了,目標(biāo)程序沒(méi)有外部依賴努酸,直接就可以運(yùn)行

缺點(diǎn):由于靜態(tài)庫(kù)會(huì)有兩份服爷,所以會(huì)導(dǎo)致目標(biāo)程序的體積增大,對(duì)內(nèi)存获诈、性能仍源、速度消耗很大

動(dòng)態(tài)庫(kù):程序編譯時(shí)并不會(huì)鏈接到目標(biāo)程序中,目標(biāo)程序只會(huì)存儲(chǔ)指向動(dòng)態(tài)庫(kù)的引用舔涎,在程序運(yùn)行時(shí)才被載入
優(yōu)勢(shì)
減少打包之后app的大辛取:因?yàn)椴恍枰截愔聊繕?biāo)程序中,所以不會(huì)影響目標(biāo)程序的體積亡嫌,與靜態(tài)庫(kù)相比嚎于,減少了app的體積大小

共享內(nèi)存,節(jié)約資源:同一份庫(kù)可以被多個(gè)程序使用

通過(guò)更新動(dòng)態(tài)庫(kù)挟冠,達(dá)到更新程序的目的:由于運(yùn)行時(shí)才載入的特性于购,可以隨時(shí)對(duì)庫(kù)進(jìn)行替換,而不需要重新編譯代碼

缺點(diǎn):動(dòng)態(tài)載入會(huì)帶來(lái)一部分性能損失知染,使用動(dòng)態(tài)庫(kù)也會(huì)使得程序依賴于外部環(huán)境肋僧,如果環(huán)境缺少了動(dòng)態(tài)庫(kù),或者庫(kù)的版本不正確控淡,就會(huì)導(dǎo)致程序無(wú)法運(yùn)行

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嫌吠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子掺炭,更是在濱河造成了極大的恐慌辫诅,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涧狮,死亡現(xiàn)場(chǎng)離奇詭異炕矮,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)勋篓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門吧享,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人譬嚣,你說(shuō)我怎么就攤上這事〕” “怎么了拜银?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵殊鞭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我尼桶,道長(zhǎng)操灿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任泵督,我火速辦了婚禮趾盐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘小腊。我一直安慰自己救鲤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布秩冈。 她就那樣靜靜地躺著本缠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪入问。 梳的紋絲不亂的頭發(fā)上丹锹,一...
    開(kāi)封第一講書(shū)人閱讀 51,245評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音芬失,去河邊找鬼楣黍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛棱烂,可吹牛的內(nèi)容都是我干的租漂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼垢啼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼窜锯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起芭析,我...
    開(kāi)封第一講書(shū)人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锚扎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后馁启,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體驾孔,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年惯疙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翠勉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡霉颠,死狀恐怖对碌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蒿偎,我是刑警寧澤朽们,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布怀读,位于F島的核電站,受9級(jí)特大地震影響骑脱,放射性物質(zhì)發(fā)生泄漏菜枷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一叁丧、第九天 我趴在偏房一處隱蔽的房頂上張望啤誊。 院中可真熱鬧,春花似錦拥娄、人聲如沸蚊锹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)枫耳。三九已至,卻和暖如春孟抗,著一層夾襖步出監(jiān)牢的瞬間迁杨,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工凄硼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铅协,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓摊沉,卻偏偏與公主長(zhǎng)得像狐史,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子说墨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354