
我們先來看看這個現(xiàn)象吧。
新建一個工程,如下,分別添加c++函數(shù),和load方法,運行程序。


查看結果

按我們一般的認知,main函數(shù)才是程序的入口,為什么會有方法在main之前就運行呢???這不是搞笑呢嗎?其實這都是有原因的,隆重請出今天的主角——dyld。
什么是dyld?
dyld(the dynamic link editor)是蘋果的動態(tài)鏈接器,是蘋果操作系統(tǒng)的重要組成部分,在app被編譯打包成可執(zhí)行文件格式的Mach-O文件后,交由dyld負責連接,加載程序
所以 App的啟動流程圖如下

由此可以看出,其實在進入main函數(shù)之前,還是有不少前戲的。那么我們就從控制臺最先打印的load方法入手吧。直接給load打個斷點,查看一下堆棧信息:

我們會發(fā)現(xiàn),是從dyld中的_dyld_start開始的,所以需要去OpenSource下載一份最新dyld的源碼來進行分析。
直接在源碼中搜索_dyld_start,可以看到不通架構,有不同的實現(xiàn),這里就只分析arm64,也就是真機的源碼哦。

如上圖,匯編前面在做一些準備工作,最終會調用dyldbootstrap::start方法,源碼中搜索dyldbootstrap,找到它的命名空間,我覺得這里的命名空間可以理解為一個類,然后找到start方法


通過注釋分析,前面是對dyld的準備,我們直接定位到dyld::_main,我們直接定位到dyld::_main函數(shù),注意這里的dyld::_main并非我們工程里的main函數(shù)。
找到dyld::_main后會發(fā)現(xiàn),這這這太tm長了吧,不想看了,嗚嗚嗚。

額,我還是簡單總結一下吧。
【第一步:環(huán)境變量配置】:根據(jù)環(huán)境變量設置相應的值以及獲取當前運行架構
【第二步:共享緩存】:檢查是否開啟了共享緩存,以及共享緩存是否映射到共享區(qū)域,例如UIKit、CoreFoundation等
【第三步:主程序的初始化】:調用instantiateFromLoadedImage函數(shù)實例化了一個ImageLoader對象,其中sniffLoadCommands函數(shù)時獲取Mach-O類型文件的Load Command的相關信息,并對其進行各種校驗
【第四步:插入動態(tài)庫】:遍歷DYLD_INSERT_LIBRARIES環(huán)境變量,調用loadInsertedDylib加載
【第五步:link 主程序】
【第六步:link 動態(tài)庫】
【第七步:弱符號綁定】
【第八步:執(zhí)行初始化方法】遞歸遍歷實例化
【第九步:尋找主程序入口即main函數(shù)】:從Load Command讀取LC_MAIN入口,如果沒有,就讀取LC_UNIXTHREAD,這樣就來到了日常開發(fā)中熟悉的main函數(shù)了
看到這好像和開頭的問題沒撒關系啊。搞什么飛機,偏題了?
問題出在我偷懶了,我們倆看看第八步的具體實現(xiàn)吧。

進入該方法

找到關鍵代碼
runInitializers,找到其實現(xiàn)
繼續(xù)往下

找到關鍵代碼

這里主要有
doInitialization和 notifySingle 兩個函數(shù),通過閱讀注釋,感覺notifySingle 是類似于通知的東西,我們來驗證一下是不是呢notifySingle 函數(shù)
全局搜索notifySingle(函數(shù),其重點是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());這句

找到如上關鍵代碼,搜索sNotifyObjCInit

發(fā)現(xiàn)只有賦值,沒有實現(xiàn),那追蹤一下是哪里賦值過來的呢。搜索
registerObjCNotifiers
發(fā)現(xiàn)是這玩意賦值的
_dyld_objc_notify_register注意:
_dyld_objc_notify_register的函數(shù)需要在libobjc源碼中搜索
然后繼續(xù)探索_dyld_objc_notify_register又是怎么調用過來的呢?繼續(xù)在dyld源碼中搜索_dyld_objc_notify_register,發(fā)現(xiàn)找不到調用了,哦嚯,就這么斷掉了?
作為要成為海賊王的男人,怎么能就這么斷掉呢???我們使用倒推大法,康康之前的堆棧信息,發(fā)現(xiàn)是objc的load_images方法調用的load方法,

這個時候需要去objc源碼中搜索load_images啦,同樣可以在Opensource上下載。
誒嘿,找到這個逼了。

哈哈哈,這玩意-
_dyld_objc_notify_register也跟著出來了。我們來理一理,_objc_init調用_dyld_objc_notify_register,將load_images作為參數(shù)傳到了dyld::registerObjCNotifiers并賦值給了sNotifyObjCInit,然后notifySingle方法調用了sNotifyObjCInit,清晰了清晰了。所以notifySingle是一個回調函數(shù)。load函數(shù)加載
下面我們進入load_images的源碼看看其實現(xiàn),以此來證明load_images中調用了所有的load函數(shù)
通過objc源碼中_objc_init源碼實現(xiàn),進入load_images的源碼實現(xiàn)

這里是通了,但好像又忽略了一個問題,_objc_init又是哪里調用過來的呢?這個時候回到剛才提到的另一個方法doInitialization。偷個懶,感興趣的朋友可以按這個探索一下。
_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)
啟動流程圖

這也就解釋了開頭打印順序的問題。