iOS程序啟動(dòng)-Dyld流程解析

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):

DYLD_PRINT_STATISTICS.png

運(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ù):

dyld_start.jpg

可以看到APP啟動(dòng)時(shí)dyld中調(diào)用函數(shù)的流程是_dyld_start -> bootstrap::start -> dyld:: _main墓陈, 順著流程分別找它們的實(shí)現(xiàn):

  • _dyld_start
_dyld_start.jpg

這里面都是匯編代碼,根據(jù)注釋大概意思是一些app相關(guān)的準(zhǔn)備工作.根據(jù)注釋可知會(huì)調(diào)用bootstrap類的start方法第献,跟調(diào)用棧是一致的贡必,因此可以直接進(jìn)入到bootstrap::start中。

  • bootstrap::start

bootstrap_start.png

圖中紅圈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 setUpinitializers兩個(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)用棧:

load_image斷點(diǎn).png

load_images調(diào)用棧.png

這個(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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市夫植,隨后出現(xiàn)的幾起案子讹剔,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件延欠,死亡現(xiàn)場(chǎng)離奇詭異陌兑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)由捎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門诀紊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人隅俘,你說(shuō)我怎么就攤上這事邻奠。” “怎么了为居?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵碌宴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蒙畴,道長(zhǎng)贰镣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任膳凝,我火速辦了婚禮碑隆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蹬音。我一直安慰自己逆航,他們只是感情好岛琼,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般胡嘿。 火紅的嫁衣襯著肌膚如雪迈套。 梳的紋絲不亂的頭發(fā)上依鸥,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天啰挪,我揣著相機(jī)與錄音,去河邊找鬼苔埋。 笑死懦砂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的组橄。 我是一名探鬼主播荞膘,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晨炕!你這毒婦竟也來(lái)了衫画?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瓮栗,失蹤者是張志新(化名)和其女友劉穎削罩,沒(méi)想到半個(gè)月后瞄勾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弥激,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年进陡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片微服。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趾疚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出以蕴,到底是詐尸還是另有隱情糙麦,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布丛肮,位于F島的核電站赡磅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宝与。R本人自食惡果不足惜焚廊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望习劫。 院中可真熱鬧咆瘟,春花似錦、人聲如沸诽里。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)须肆。三九已至匿乃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間豌汇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工泄隔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拒贱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓佛嬉,卻偏偏與公主長(zhǎng)得像逻澳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子暖呕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容