iOS程序啟動(dòng)流程概覽
什么是Dyld? 它跟程序的啟動(dòng)有什么關(guān)系擒悬?
Dyld是動(dòng)態(tài)庫(kù)鏈接器啸臀。在程序啟動(dòng)過(guò)程中負(fù)責(zé)加載所有庫(kù)和可執(zhí)行文件邻梆。在此過(guò)程中完成對(duì)這些庫(kù)和可執(zhí)行文件的符號(hào)重定向(Rebase)和符號(hào)綁定(Binding)等操作玻募。接下來(lái)我們可以通過(guò)配置xcode的環(huán)境變量大致查看App啟動(dòng)流程览濒。
打印iOS程序啟動(dòng)流程
iOS APP的啟動(dòng)流程可以通過(guò)在xcode的Edit Scheme ->Run->Arguments -> Environment Variables中加入環(huán)境變量DYLD_PRINT_STATISTICS打印出來(lái):
運(yùn)行APP,打印結(jié)果如下:
Total pre-main time: 31.70 milliseconds (100.0%)
dylib loading time: 27.29 milliseconds (86.0%)
rebase/binding time: 411015771.5 seconds (61552300.3%)
ObjC setup time: 78.12 milliseconds (246.4%)
initializer time: 74.97 milliseconds (236.4%)
slowest intializers :
libSystem.B.dylib : 6.10 milliseconds (19.2%)
libBacktraceRecording.dylib : 7.03 milliseconds (22.1%)
libobjc.A.dylib : 1.69 milliseconds (5.3%)
libMainThreadChecker.dylib : 57.82 milliseconds (182.3%)
從結(jié)果可以看出四個(gè)階段的名稱以及它們對(duì)應(yīng)的耗時(shí)反肋。
dylib loading 階段是動(dòng)態(tài)鏈接庫(kù)dylib加載動(dòng)態(tài)庫(kù)的階段那伐,包括系統(tǒng)動(dòng)態(tài)庫(kù)和我們自己的動(dòng)態(tài)庫(kù)。
rebase/binding這個(gè)階段實(shí)際就是兩個(gè)操作rebase和binding石蔗。rebase就是內(nèi)部符號(hào)偏移修正罕邀,binding是外部符號(hào)綁定。
ObjC setup這一階段主要是OC類相關(guān)的事務(wù)养距,比如類的注冊(cè)诉探,category、protocol的讀取等等棍厌。
intializers 程序的初始化肾胯,包括所依賴的動(dòng)態(tài)庫(kù)的初始化。在這期間會(huì)調(diào)用 Objc 類的 + load 函數(shù)耘纱,調(diào)用 C++ 中帶有constructor 標(biāo)記的函數(shù)等敬肚。
上面打印出的這四個(gè)階段還是比較粗略的,實(shí)際上dyld在啟動(dòng)過(guò)程中是比較復(fù)雜的束析。如果將上面的DYLD_PRINT_STATISTICS換成DYLD_PRINT_STATISTICS_DETAILS艳馒,打印得會(huì)更加詳細(xì):
total time: 1.3 seconds (100.0%)
total images loaded: 398 (392 from dyld shared cache)
total segments mapped: 21, into 415 pages
total images loading time: 944.18 milliseconds (68.5%)
total load time in ObjC: 80.88 milliseconds (5.8%)
total debugger pause time: 801.87 milliseconds (58.2%)
total dtrace DOF registration time: 0.00 milliseconds (0.0%)
total rebase fixups: 16,279
total rebase fixups time: 7.47 milliseconds (0.5%)
total binding fixups: 567,346
total binding fixups time: 275.57 milliseconds (20.0%)
total weak binding fixups time: 0.03 milliseconds (0.0%)
total redo shared cached bindings time: 459.30 milliseconds (33.3%)
total bindings lazily fixed up: 0 of 0
total time in initializers and ObjC +load: 68.67 milliseconds (4.9%)
libSystem.B.dylib : 6.04 milliseconds (0.4%)
libBacktraceRecording.dylib : 7.01 milliseconds (0.5%)
libMainThreadChecker.dylib : 52.05 milliseconds (3.7%)
total symbol trie searches: 1378030
total symbol table binary searches: 0
total images defining weak symbols: 50
total images using weak symbols: 110
從打印結(jié)果可以看出,實(shí)際上也是上面四個(gè)階段的擴(kuò)展员寇。比如說(shuō)dyld loading包含了images loaded(共享緩存)和images loading等弄慰。這幾個(gè)階段都是通過(guò)dyld來(lái)完成的第美。那dyld到底具體做些什么呢?接下來(lái)就通過(guò)dyld源碼來(lái)分析app啟動(dòng)過(guò)程dyld到底做了哪些事情曹动。
斷點(diǎn)查看啟動(dòng)時(shí)dyld的函數(shù)調(diào)用棧斋日,查找源碼位置
首先我們?cè)?load方法打一個(gè)斷點(diǎn)牲览。然后通過(guò)函數(shù)調(diào)用棧找出啟動(dòng)過(guò)程中dyld都執(zhí)行了那些函數(shù):
可以看到APP啟動(dòng)時(shí)dyld中調(diào)用函數(shù)的流程是_dyld_start -> bootstrap::start -> dyld:: _main墓陈, 順著流程分別找它們的實(shí)現(xiàn):
- _dyld_start
這里面都是匯編代碼,根據(jù)注釋大概意思是一些app相關(guān)的準(zhǔn)備工作.根據(jù)注釋可知會(huì)調(diào)用bootstrap類的start方法第献,跟調(diào)用棧是一致的贡必,因此可以直接進(jìn)入到bootstrap::start中。
- bootstrap::start
圖中紅圈1:這個(gè)方法的rebaseDyld是dyld完成自身重定位的方法庸毫。首先dyld本身也是一個(gè)動(dòng)態(tài)庫(kù)仔拟。對(duì)于普通動(dòng)態(tài)庫(kù),符號(hào)重定位可以由dyld來(lái)加載鏈接來(lái)完成飒赃,但是dyld的重定位誰(shuí)來(lái)做利花?只能是它自身完成。這就是為什么會(huì)有rebaseDyld的原因载佳,它其實(shí)是在對(duì)自身進(jìn)行重定位炒事,只有完成了自身的重定位它才能使用全局變量和靜態(tài)變量。
圖中紅圈2:部分就是進(jìn)入了 dyld:: _main函數(shù)了蔫慧。dyld在app啟動(dòng)中做的主要工作基本上都是在這里完成的挠乳。
-
dyld:: _main
App啟動(dòng)過(guò)程中Dyld的主要工作都是在這個(gè)dyld:: _main函數(shù)中完成的。函數(shù)中代碼比較多姑躲,現(xiàn)在大概整理出如下流程:
1睡扬、環(huán)境變量配置
2、加載共享緩存
3黍析、實(shí)例化主程序卖怜,并添加到allImages列表中;
4阐枣、加載動(dòng)態(tài)庫(kù)插入的動(dòng)態(tài)庫(kù)马靠,并添加到allImages列表中;
5侮繁、鏈接主程序(完成Rebase/Binding/weakBind)
6虑粥、主程序初始化(ObjC setup/intializers是在這一過(guò)程進(jìn)行的)
7、查找并返回主程序的main函數(shù)地址
APP啟動(dòng)流程詳解
1宪哩、環(huán)境變量配置
環(huán)境變量是存在于系統(tǒng)中的一些共用數(shù)據(jù)娩贷,任何程序都可以訪問(wèn)。通常來(lái)說(shuō)锁孟,環(huán)境變量存儲(chǔ)的都是一些系統(tǒng)的公共信息彬祖,例如系統(tǒng)搜索路徑茁瘦,當(dāng)前OS版本等。
// 主程序文件頭部信息
sMainExecutableMachHeader = mainExecutableMH;
// 隨機(jī)偏移值A(chǔ)SLR
sMainExecutableSlide = mainExecutableSlide;
setContext(mainExecutableMH, argc, argv, envp, apple);
2储笑、共享緩存
當(dāng)我們構(gòu)建一個(gè)APP的時(shí)候甜熔,會(huì)鏈接各種各樣的庫(kù)。這些庫(kù)同樣又會(huì)依賴其他的一些框架和動(dòng)態(tài)鏈接庫(kù)突倍。于是要加載的動(dòng)態(tài)鏈接庫(kù)會(huì)非常多腔稀。同樣非獨(dú)立的符號(hào)也非常多。這里就會(huì)有成千上萬(wàn)的符號(hào)要確定羽历。這個(gè)工作將會(huì)話費(fèi)很多時(shí)間——幾秒鐘焊虏。 為了優(yōu)化這個(gè)過(guò)程,OS X和iOS上動(dòng)態(tài)鏈接器使用了一個(gè)共享緩存秕磷,在/var/db/dyld/诵闭。對(duì)于每一種架構(gòu),操作系統(tǒng)有一個(gè)單獨(dú)的文件包含了絕大多數(shù)的動(dòng)態(tài)鏈接庫(kù)澎嚣,這些庫(kù)已經(jīng)互相連接并且符號(hào)都已經(jīng)確定疏尿。當(dāng)一個(gè)Mach-o文件被加載的時(shí)候,動(dòng)態(tài)鏈接器會(huì)首先檢查共享緩存易桃,如果存在相應(yīng)的庫(kù)就用褥琐。每一個(gè)進(jìn)程都把這個(gè)共享緩存映射到了自己的地址空間中。這個(gè)方法優(yōu)化了OS X和iOS上程序的加載時(shí)間颈抚。以下是讀取緩存的代碼:
// load shared cache
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
mapSharedCache();
}
3踩衩、實(shí)例化主程序
初始化一個(gè)ImageLoader類型的主程序sMainExecutable。后面的主程序相關(guān)的操作包括初始化都是通過(guò)這個(gè)對(duì)象來(lái)完成的贩汉。mainExecutableMH是mach-o文件頭部信息驱富,mainExecutableSlide主程序隨機(jī)偏移值,sExecPath程序的路徑匹舞。初始化之后檢查應(yīng)用簽名褐鸥。
//
// instantiate ImageLoader for main executable
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
4、加載插入的動(dòng)態(tài)庫(kù)
這里會(huì)遍歷所有插入的動(dòng)態(tài)庫(kù)(dylib)赐稽,然后加載叫榕。
// load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
5、鏈接主程序
這一步主要實(shí)現(xiàn)過(guò)程如下:
5.1姊舵、主程序及其依賴的動(dòng)態(tài)庫(kù)的Rebase
5.2晰绎、插入動(dòng)態(tài)庫(kù)的Rebase
5.3、主程序及其依賴的動(dòng)態(tài)庫(kù)的Binding
5.4括丁、插入動(dòng)態(tài)庫(kù)的Binding
5.5荞下、弱綁定weakBind
備注:這里的recursiveBind和recursiveRebase都是遞歸的,所以會(huì)自底向上地分別調(diào)用 doRebase() 和 doBind() 方法,這樣被依賴的動(dòng)態(tài)庫(kù)總是先于依賴它的動(dòng)態(tài)庫(kù)執(zhí)行 rebase 和 binding尖昏。因?yàn)橹挥兴蕾嚨淖觿?dòng)態(tài)庫(kù)rebase和binding之后仰税,符號(hào)地址才能被確定,這時(shí)候主動(dòng)態(tài)庫(kù)再進(jìn)行binding就可以直接映射到相應(yīng)的子動(dòng)態(tài)庫(kù)的符號(hào)地址抽诉。
-
5.1陨簇、主程序及其依賴的動(dòng)態(tài)庫(kù)的Rebase
主程序調(diào)用link函數(shù)進(jìn)行鏈接。其中重定向rebase就是在這一步完成的迹淌,過(guò)程是link->recursiveRebaseWithAccounting->recursiveRebase河绽。recursiveRebase函數(shù)會(huì)遞歸對(duì)主程序及其所依賴的庫(kù)和執(zhí)行文件進(jìn)行符號(hào)重定向(Rebase)。
// link main executable
gLinkContext.linkingMainExecutable = true;
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
這其中雖然調(diào)用了link函數(shù)巍沙,但是這里主程序link函數(shù)中并沒(méi)有調(diào)用recursiveBindWithAccounting函數(shù)葵姥。因?yàn)閘ink函數(shù)內(nèi)部判斷l(xiāng)inkingMainExecutable為false才執(zhí)行recursiveBindWithAccounting荷鼠,而此時(shí)linkingMainExecutable為true句携。以下是link函數(shù)的主要實(shí)現(xiàn):
t2 = mach_absolute_time();
this->recursiveRebaseWithAccounting(context);
context.notifyBatch(dyld_image_state_rebased, false);
t3 = mach_absolute_time();
if ( !context.linkingMainExecutable )
this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
t4 = mach_absolute_time();
if ( !context.linkingMainExecutable ) this ->weakBind(context);
t5 = mach_absolute_time();
-
5.2、插入動(dòng)態(tài)庫(kù)的Rebase
鏈接插入的動(dòng)態(tài)庫(kù)允乐,跟上一步一樣都是調(diào)用link函數(shù)矮嫉。
// link any inserted libraries
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
image->setNeverUnloadRecursive();
}
if ( gLinkContext.allowInterposing ) {
// only INSERTED libraries can interpose
// register interposing info after all inserted libraries are bound so chaining works
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->registerInterposing(gLinkContext);
}
}
}
-
5.3、主程序及其依賴的動(dòng)態(tài)庫(kù)的Binding
調(diào)用函數(shù)recursiveBindWithAccounting會(huì)遞歸對(duì)主程序及其所依賴的動(dòng)態(tài)庫(kù)進(jìn)行符號(hào)綁定(Binding)牍疏。
/ Bind and notify for the main executable now that interposing has been registered
uint64_t bindMainExecutableStartTime = mach_absolute_time();
sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
- 5.4蠢笋、插入動(dòng)態(tài)庫(kù)的Binding
// Bind and notify for the inserted images now interposing has been registered
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
}
}
-
5.5、弱綁定weakBind
插入的動(dòng)態(tài)庫(kù)鏈接完成后就執(zhí)行主程序的弱綁定鳞陨,之后就會(huì)把狀態(tài)linkingMainExecutable設(shè)置為false昨寞,表示主程序鏈接完成。
// <rdar://problem/12186933> do weak binding only after all inserted images linked
sMainExecutable->weakBind(gLinkContext);
gLinkContext.linkingMainExecutable = false;
6厦滤、主程序初始化
完成了rebase/binding之后就開始了初始化工作援岩。通過(guò)調(diào)用函數(shù) initializeMainExecutable進(jìn)行初始化:
// run all initializers
initializeMainExecutable();
主程序初始化初始是app啟動(dòng)很重要的一個(gè)過(guò)程,在主程序初始化的過(guò)程中會(huì)同時(shí)初始化其所依賴的動(dòng)態(tài)庫(kù)掏导,這一過(guò)程完成了包括ObjC的初始化等工作享怀。+load和C++構(gòu)造函數(shù)也是在這一過(guò)程中完成的。
initializeMainExecutable函數(shù)的初始流程大概整理如下:
dyld_sim`ImageLoader::runInitializers
dyld_sim`ImageLoader::processInitializers
dyld_sim`ImageLoader::recursiveInitialization
dyld_sim`dyld::notifySingle
libobjc.A.dylib`load_images
libobjc.A.dylib`call_load_methods
libobjc.A.dylib`call_class_loads
+ load
libobjc.A.dylib`call_category_loads
+ load
/*備注:這里面可以看到趟咆,先調(diào)用所有class的+load再調(diào)用category的添瓷。因此我們本類的+load方法總是先于category被調(diào)用。*/
/**下面是調(diào)用C++構(gòu)造函數(shù)的流程*/
dyld_sim`ImageLoaderMachO::doInitialization
dyld_sim`ImageLoaderMachO::doModInitFunctions
__attribute__((constructor)) void func(void)
主程序在初始化時(shí)是先遞歸調(diào)用其所依賴的動(dòng)態(tài)庫(kù)完成初始化然后在完成自身初始化值纱。在上面的函數(shù)流程中l(wèi)oad_images函數(shù)是在libobjc.A.dylib動(dòng)態(tài)庫(kù)里面的鳞贷,它是在程序初始化之前被注冊(cè)到dyld里面,等到初始化的時(shí)候執(zhí)行虐唠,完成libobjc的映射搀愧,非懶加載類就是在這一過(guò)程中被加載并調(diào)用+load方法的。以下是initializeMainExecutable源碼如下:
void initializeMainExecutable()
{
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// run initializers for main executable and everything it brings up
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
if ( gLibSystemHelpers != NULL )
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
通過(guò)源碼分析:首先是遍歷插入的動(dòng)態(tài)庫(kù)進(jìn)行初始化,然后就是主程序的初始化妈橄。到這里程序的初始化就算完成了庶近,這一階段其實(shí)就是完成了我們之前打印的ObjC setUp 和initializers兩個(gè)階段。主程序初始化完成之后就進(jìn)入眷蚓,dyld就通知各方主程序準(zhǔn)備要調(diào)用main函數(shù)了鼻种,然后開始尋找主程序的main函數(shù)入口地址返回。
7沙热、找到主程序的main函數(shù)地址并返回
完成主程序初始化之后叉钥,dyld就可以從主程序找到入口main函數(shù),然后返回篙贸。
// main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
另外程序啟動(dòng)過(guò)程中一些重要函數(shù)的解析
通過(guò)load_images作為符號(hào)斷點(diǎn)查看調(diào)用棧投队,可以得到更詳細(xì)的調(diào)用棧:
這個(gè)過(guò)程中涉及到幾個(gè)重要的動(dòng)態(tài)庫(kù)和方法,主要有
動(dòng)態(tài)庫(kù):
libSystem.B.dylib爵川,系統(tǒng)基礎(chǔ)動(dòng)態(tài)庫(kù)敷鸦,其他動(dòng)態(tài)庫(kù)的初始化必須在他之后。其初始化函數(shù)是libSystem_initializer寝贡;
libdispatch.dylib扒披,GCD所在的庫(kù),其初始化函數(shù)是libdispatch_init圃泡;
libobjc.dylib碟案,OC代碼庫(kù),其初始化函數(shù)是_objc_init 颇蜡。
幾個(gè)重要的函數(shù):
_objc_init价说;
_dyld_objc_notify_register;
map_images风秤;
load_images鳖目;
_objc_init
_objc_init是libobjc.A.dylib的初始化函數(shù),其源碼如下:
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();//環(huán)境變量相關(guān)初始化唁情,逼比如我們之前添加的打印程序啟動(dòng)的環(huán)境變量就是在這里面處理的
tls_init();
static_init();// static_init() Run C++ static constructor functions. libc calls _objc_init() before dyld would call our static constructors, so we have to do it ourselves.
runtime_init(); // 類表allocatedClasses.init()和unattachedCategories.init(32)看似跟category相關(guān)的表
exception_init();//異常處理回調(diào)函數(shù)初始化
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
總結(jié)如下:
environ_init()
讀取影響運(yùn)行時(shí)的環(huán)境變量疑苔。如果需要,還可以打印環(huán)境變量幫助甸鸟。
tls_init()
關(guān)于線程key的綁定 - 比如每條線程數(shù)據(jù)的析構(gòu)函數(shù)惦费。
static_init()
有注釋可知運(yùn)行C++靜態(tài)析構(gòu)函數(shù)。在dyld調(diào)用我們的靜態(tài)構(gòu)造函數(shù)之前抢韭,libc會(huì)調(diào)用_objc_init(), 因此我們必須自己調(diào)用薪贫。
runtime_init()
類表allocatedClasses.init()和unattachedCategories.init(32)看似跟category相關(guān)的表,這個(gè)在后面類的
imp_implementationWithBlock_init(void)
啟動(dòng)回調(diào)機(jī)制刻恭。通常不會(huì)這么做瞧省。因?yàn)樗械某跏荚挾际嵌栊缘某敦玻菍?duì)于某些進(jìn)程,我們會(huì)迫不及待地加載trampolines dylib鞍匾。
_dyld_objc_notify_register
向dyld注冊(cè)監(jiān)聽函數(shù)交洗。以便在鏡像文件映射、取消映射和初始化objc時(shí)調(diào)用橡淑。Dyld將使用包含objc-image-info的鏡像文件的數(shù)組回調(diào)給map_images函數(shù)构拳。
runtime_init()
運(yùn)行時(shí)環(huán)境初始化,里面主要是類表allocatedClasses 和unattachedCategories
exception_init()
初始化ibobjc的異常處理系統(tǒng)
_dyld_objc_notify_register
函數(shù)_dyld_objc_notify_register是負(fù)責(zé)向dyld注冊(cè)相關(guān)的回調(diào)函數(shù)梁棠,這些函數(shù)會(huì)在合適的時(shí)機(jī)dyld會(huì)通知調(diào)用置森,這函數(shù)就是它的三個(gè)參數(shù)load_images、map_images和unmap_image符糊。其實(shí)現(xiàn)是在dlyd中的:
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
可以看到dyld會(huì)把這三個(gè)函數(shù)緩存起來(lái)凫海,以方便在合適的時(shí)機(jī)調(diào)用它們。
map_images(ObjC setUp)
函數(shù)map_images是在libobjc庫(kù)中實(shí)現(xiàn)的男娄,是在libobjc初始化的時(shí)候通過(guò)_dyld_objc_notify_register向dlyd注冊(cè)行贪,用于監(jiān)聽鏡像文件的映射。OC相關(guān)的鏡像文件在完成rebase/binding之后沪伙,就會(huì)發(fā)出一個(gè)通知去調(diào)用map_images瓮顽。但是有些時(shí)候鏡像文件是在libobjc初始化之前完成rebase/binding,這時(shí)候還沒(méi)有map_images這個(gè)函數(shù)围橡。所以在libobjc初始化并調(diào)用 _dyld_objc_notify_register向dyld注冊(cè)的map_images時(shí)候,它會(huì)去把已經(jīng)完成rebase/binding的鏡像文件通過(guò)調(diào)用map_images進(jìn)行OC代碼相關(guān)的映射映射缕贡。所以map_images的調(diào)用是在rebase/binding之后翁授,在動(dòng)態(tài)庫(kù)(鏡像文件)調(diào)用initializer初始化之前的。函數(shù)map_images主要完成動(dòng)態(tài)庫(kù)中的類的映射晾咪、初始化等操作收擦。也就是我們前面打印的ObjC setUp階段。map_images通過(guò)鏡像文件的讀取類相關(guān)的信息進(jìn)行初始化谍倦,然后保存在類表中塞赂。
在完成類的映射,并完成非懶加載類的加載之后就可以調(diào)用OC相關(guān)的動(dòng)態(tài)庫(kù)的初始化方法initializer了昼蛀。initializer完成之后就會(huì)調(diào)用load_images函數(shù)宴猾。
load_images
load_images函數(shù)是在OC相關(guān)動(dòng)態(tài)庫(kù)完成初始化之后調(diào)用的,在這期間叼旋,這里首先會(huì)加載非懶加載類的category仇哆,然后調(diào)用所有已經(jīng)加載的class和category的+load方法(點(diǎn)擊了解類的加載流程):
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}