我們先來(lái)看看這個(gè)現(xiàn)象吧嬉橙。
新建一個(gè)工程,如下俺陋,分別添加c++函數(shù),和load方法洛史,運(yùn)行程序。
查看結(jié)果
按我們一般的認(rèn)知酱吝,main
函數(shù)才是程序的入口也殖,為什么會(huì)有方法在main
之前就運(yùn)行呢?务热?忆嗜?這不是搞笑呢嗎?其實(shí)這都是有原因的崎岂,隆重請(qǐng)出今天的主角——dyld捆毫。
什么是dyld?
dyld(the dynamic link editor)是蘋(píng)果的動(dòng)態(tài)鏈接器冲甘,是蘋(píng)果操作系統(tǒng)的重要組成部分冻璃,在app被編譯打包成可執(zhí)行文件格式的Mach-O文件后,交由dyld負(fù)責(zé)連接损合,加載程序
所以 App的啟動(dòng)流程圖如下
由此可以看出省艳,其實(shí)在進(jìn)入main
函數(shù)之前,還是有不少前戲的嫁审。那么我們就從控制臺(tái)最先打印的load
方法入手吧跋炕。直接給load
打個(gè)斷點(diǎn),查看一下堆棧信息:
我們會(huì)發(fā)現(xiàn)律适,是從dyld中的_dyld_start開(kāi)始的辐烂,所以需要去OpenSource下載一份最新dyld的源碼來(lái)進(jìn)行分析。
直接在源碼中搜索_dyld_start
捂贿,可以看到不通架構(gòu)纠修,有不同的實(shí)現(xiàn),這里就只分析arm64
厂僧,也就是真機(jī)的源碼哦扣草。
如上圖,匯編前面在做一些準(zhǔn)備工作颜屠,最終會(huì)調(diào)用dyldbootstrap::start
方法辰妙,源碼中搜索dyldbootstrap
,找到它的命名空間
,我覺(jué)得這里的命名空間
可以理解為一個(gè)類(lèi),然后找到start
方法
通過(guò)注釋分析甫窟,前面是對(duì)dyld的準(zhǔn)備密浑,我們直接定位到dyld::_main
,我們直接定位到dyld::_main
函數(shù),注意這里的dyld::_main
并非我們工程里的main
函數(shù)粗井。
找到dyld::_main
后會(huì)發(fā)現(xiàn)尔破,這這這太tm長(zhǎng)了吧街图,不想看了,嗚嗚嗚懒构。
額餐济,我還是簡(jiǎn)單總結(jié)一下吧。
【第一步:環(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ì)象赞赖,其中sniffLoadCommands函數(shù)時(shí)獲取Mach-O類(lèi)型文件的Load Command的相關(guān)信息,并對(duì)其進(jìn)行各種校驗(yàn)
【第四步:插入動(dòng)態(tài)庫(kù)】:遍歷DYLD_INSERT_LIBRARIES環(huán)境變量冤灾,調(diào)用loadInsertedDylib加載
【第五步:link 主程序】
【第六步:link 動(dòng)態(tài)庫(kù)】
【第七步:弱符號(hào)綁定】
【第八步:執(zhí)行初始化方法】遞歸遍歷實(shí)例化
【第九步:尋找主程序入口即main函數(shù)】:從Load Command讀取LC_MAIN入口前域,如果沒(méi)有,就讀取LC_UNIXTHREAD韵吨,這樣就來(lái)到了日常開(kāi)發(fā)中熟悉的main函數(shù)了
看到這好像和開(kāi)頭的問(wèn)題沒(méi)撒關(guān)系啊匿垄。搞什么飛機(jī),偏題了归粉?
問(wèn)題出在我偷懶了椿疗,我們倆看看第八步的具體實(shí)現(xiàn)吧。
進(jìn)入該方法
找到關(guān)鍵代碼
runInitializers
,找到其實(shí)現(xiàn)繼續(xù)往下
找到關(guān)鍵代碼
這里主要有
doInitialization
和 notifySingle
兩個(gè)函數(shù)糠悼,通過(guò)閱讀注釋?zhuān)杏X(jué)notifySingle
是類(lèi)似于通知的東西届榄,我們來(lái)驗(yàn)證一下是不是呢notifySingle 函數(shù)
全局搜索notifySingle(函數(shù),其重點(diǎn)是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());這句
找到如上關(guān)鍵代碼,搜索sNotifyObjCInit
發(fā)現(xiàn)只有賦值倔喂,沒(méi)有實(shí)現(xiàn)铝条,那追蹤一下是哪里賦值過(guò)來(lái)的呢。搜索
registerObjCNotifiers
發(fā)現(xiàn)是這玩意賦值的
_dyld_objc_notify_register
注意:
_dyld_objc_notify_register
的函數(shù)需要在libobjc源碼中搜索
然后繼續(xù)探索_dyld_objc_notify_register
又是怎么調(diào)用過(guò)來(lái)的呢席噩?繼續(xù)在dyld
源碼中搜索_dyld_objc_notify_register
,發(fā)現(xiàn)找不到調(diào)用了班缰,哦嚯,就這么斷掉了悼枢?
作為要成為海賊王的男人埠忘,怎么能就這么斷掉呢?馒索?给梅?我們使用倒推大法,康康之前的堆棧信息双揪,發(fā)現(xiàn)是objc
的load_images
方法調(diào)用的load
方法动羽,
這個(gè)時(shí)候需要去objc源碼中搜索load_images
啦,同樣可以在Opensource上下載渔期。
誒嘿运吓,找到這個(gè)逼了渴邦。
哈哈哈,這玩意-
_dyld_objc_notify_register
也跟著出來(lái)了拘哨。我們來(lái)理一理谋梭,_objc_init
調(diào)用_dyld_objc_notify_register
,將load_images
作為參數(shù)傳到了dyld::registerObjCNotifiers
并賦值給了sNotifyObjCInit
,然后notifySingle
方法調(diào)用了sNotifyObjCInit
倦青,清晰了清晰了瓮床。所以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)
這里是通了,但好像又忽略了一個(gè)問(wèn)題癣亚,_objc_init
又是哪里調(diào)用過(guò)來(lái)的呢丑掺?這個(gè)時(shí)候回到剛才提到的另一個(gè)方法doInitialization
。偷個(gè)懶述雾,感興趣的朋友可以按這個(gè)探索一下街州。
_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)
啟動(dòng)流程圖
這也就解釋了開(kāi)頭打印順序的問(wèn)題。