簡(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í)行文件
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) -
【第二步:
共享緩存
】:檢查是否開(kāi)啟了共享緩存测蘑,以及共享緩存是否映射到共享區(qū)域灌危,例如UIKit
、CoreFoundation
等 -
【第三步:
主程序的初始化
】:調(diào)用instantiateFromLoadedImage
函數(shù)實(shí)例化了一個(gè)ImageLoader
對(duì)象 -
【第四步:
插入動(dòng)態(tài)庫(kù)
】:遍歷DYLD_INSERT_LIBRARIES
環(huán)境變量碳胳,調(diào)用loadInsertedDylib
加載 -
【第五步:
link 主程序
】 -
【第六步:
link 動(dòng)態(tài)庫(kù)
】 -
【第七步:
弱符號(hào)綁定
】 -
【第八步:
執(zhí)行初始化方法
】 -
【第九步:
尋找主程序入口
即main
函數(shù)】:從Load Command
讀取LC_MAIN
入口勇蝙,如果沒(méi)有,就讀取LC_UNIXTHREAD
挨约,這樣就來(lái)到了日常開(kāi)發(fā)中熟悉的main
函數(shù)了
-
第八步:執(zhí)行初始化方法
-
進(jìn)入
initializeMainExecutable
源碼,主要是循環(huán)遍歷
味混,都會(huì)執(zhí)行runInitializers
方法 -
全局搜索
runInitializers(cons
,找到如下源碼,其核心代碼是processInitializers
函數(shù)的調(diào)用 -
進(jìn)入
processInitializers
函數(shù)的源碼實(shí)現(xiàn)烫罩,其中對(duì)鏡像列表調(diào)用recursiveInitialization
函數(shù)進(jìn)行遞歸實(shí)例化 -
全局搜索
recursiveInitialization(cons
函數(shù),其源碼實(shí)現(xiàn)如下
在這里惜傲,需要分成兩部分探索,一部分是notifySingle
函數(shù)贝攒,一部分是doInitialization
函數(shù),首先探索notifySingle
函數(shù)
notifySingle 函數(shù)
-
全局搜索
notifySingle(
函數(shù),其重點(diǎn)是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
這句 -
全局搜索
sNotifyObjCInit
时甚,發(fā)現(xiàn)沒(méi)有找到實(shí)現(xiàn)隘弊,有賦值操作 -
搜索
registerObjCNotifiers
在哪里調(diào)用了,發(fā)現(xiàn)在_dyld_objc_notify_register
進(jìn)行了調(diào)用注意:
_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ù)
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) -
進(jìn)入
call_load_methods
源碼實(shí)現(xiàn)树埠,可以發(fā)現(xiàn)其核心是通過(guò)do-while
循環(huán)調(diào)用+load
方法 -
進(jìn)入
call_class_loads
源碼實(shí)現(xiàn),了解到這里調(diào)用的load
方法證實(shí)我們前文提及的類的load
方法
所以嘶伟,load_images
調(diào)用了所有的load
函數(shù)怎憋,以上的源碼分析過(guò)程正好對(duì)應(yīng)堆棧的打印信息
【總結(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
-
進(jìn)入
doInitialization
函數(shù)的源碼實(shí)現(xiàn)這里也需要分成兩部分癌别,一部分是
doImageInit
函數(shù),一部分是doModInitFunctions
函數(shù)-
進(jìn)入
doImageInit
源碼實(shí)現(xiàn)蹋笼,其核心主要是for循環(huán)加載方法的調(diào)用
规个,這里需要注意的一點(diǎn)是,libSystem
的初始化必須先運(yùn)行
-
進(jìn)入
doModInitFunctions
源碼實(shí)現(xiàn)姓建,這個(gè)方法中加載了所有Cxx
文件可以通過(guò)測(cè)試程序的堆棧信息來(lái)驗(yàn)證,在C++方法處加一個(gè)斷點(diǎn)
-
走到這里诞仓,還是沒(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
斷住后的堆棧信息 -
在
libsystem
中查找libSystem_initializer
镀钓,查看其中的實(shí)現(xiàn) -
根據(jù)前面的堆棧信息穗熬,我們發(fā)現(xiàn)走的是
libSystem_initializer
中會(huì)調(diào)用libdispatch_init
函數(shù),而這個(gè)函數(shù)的源碼是在libdispatch
開(kāi)源庫(kù)中的丁溅,在libdispatch
中搜索libdispatch_init
-
進(jìn)入
_os_object_init
源碼實(shí)現(xiàn)唤蔗,其源碼實(shí)現(xiàn)調(diào)用了_objc_init
函數(shù)結(jié)合上面的分析,從初始化
_objc_init
注冊(cè)的_dyld_objc_notify_register
的參數(shù)2窟赏,即load_images
妓柜,到sNotifySingle
-->sNotifyObjCInie=參數(shù)2
到sNotifyObjcInit()
調(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)
補(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)行