iOS程序啟動-Dyld流程解析

iOS程序啟動流程概覽

什么是Dyld? 它跟程序的啟動有什么關(guān)系?

Dyld是動態(tài)庫鏈接器。在程序啟動過程中負(fù)責(zé)加載所有庫和可執(zhí)行文件。在此過程中完成對這些庫和可執(zhí)行文件的符號重定向(Rebase)和符號綁定(Binding)等操作。接下來我們可以通過配置xcode的環(huán)境變量大致查看App啟動流程。

打印iOS程序啟動流程

iOS APP的啟動流程可以通過在xcode的Edit Scheme ->Run->Arguments -> Environment Variables中加入環(huán)境變量DYLD_PRINT_STATISTICS打印出來:

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é)果可以看出四個階段的名稱以及它們對應(yīng)的耗時。

dylib loading 階段是動態(tài)鏈接庫dylib加載動態(tài)庫的階段,包括系統(tǒng)動態(tài)庫和我們自己的動態(tài)庫。
rebase/binding這個階段實(shí)際就是兩個操作rebase和binding。rebase就是內(nèi)部符號偏移修正,binding是外部符號綁定。
ObjC setup這一階段主要是OC類相關(guān)的事務(wù),比如類的注冊,category、protocol的讀取等等。
intializers 程序的初始化,包括所依賴的動態(tài)庫的初始化。在這期間會調(diào)用 Objc 類的 + load 函數(shù),調(diào)用 C++ 中帶有constructor 標(biāo)記的函數(shù)等。

上面打印出的這四個階段還是比較粗略的,實(shí)際上dyld在啟動過程中是比較復(fù)雜的。如果將上面的DYLD_PRINT_STATISTICS換成DYLD_PRINT_STATISTICS_DETAILS,打印得會更加詳細(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í)際上也是上面四個階段的擴(kuò)展。比如說dyld loading包含了images loaded(共享緩存)和images loading等。這幾個階段都是通過dyld來完成的。那dyld到底具體做些什么呢?接下來就通過dyld源碼來分析app啟動過程dyld到底做了哪些事情。

斷點(diǎn)查看啟動時dyld的函數(shù)調(diào)用棧,查找源碼位置

首先我們在+load方法打一個斷點(diǎn)。然后通過函數(shù)調(diào)用棧找出啟動過程中dyld都執(zhí)行了那些函數(shù):

dyld_start.jpg

可以看到APP啟動時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ù)注釋可知會調(diào)用bootstrap類的start方法,跟調(diào)用棧是一致的,因此可以直接進(jìn)入到bootstrap::start中。

  • bootstrap::start

bootstrap_start.png

圖中紅圈1:這個方法的rebaseDyld是dyld完成自身重定位的方法。首先dyld本身也是一個動態(tài)庫。對于普通動態(tài)庫,符號重定位可以由dyld來加載鏈接來完成,但是dyld的重定位誰來做?只能是它自身完成。這就是為什么會有rebaseDyld的原因,它其實(shí)是在對自身進(jìn)行重定位,只有完成了自身的重定位它才能使用全局變量和靜態(tài)變量。
圖中紅圈2:部分就是進(jìn)入了 dyld:: _main函數(shù)了。dyld在app啟動中做的主要工作基本上都是在這里完成的。

  • dyld:: _main
    App啟動過程中Dyld的主要工作都是在這個dyld:: _main函數(shù)中完成的。函數(shù)中代碼比較多,現(xiàn)在大概整理出如下流程:

1、環(huán)境變量配置
2、加載共享緩存
3、實(shí)例化主程序,并添加到allImages列表中;
4、加載動態(tài)庫插入的動態(tài)庫,并添加到allImages列表中;
5、鏈接主程序(完成Rebase/Binding/weakBind)
6、主程序初始化(ObjC setup/intializers是在這一過程進(jìn)行的)
7、查找并返回主程序的main函數(shù)地址

APP啟動流程詳解

1、環(huán)境變量配置

環(huán)境變量是存在于系統(tǒng)中的一些共用數(shù)據(jù),任何程序都可以訪問。通常來說,環(huán)境變量存儲的都是一些系統(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)建一個APP的時候,會鏈接各種各樣的庫。這些庫同樣又會依賴其他的一些框架和動態(tài)鏈接庫。于是要加載的動態(tài)鏈接庫會非常多。同樣非獨(dú)立的符號也非常多。這里就會有成千上萬的符號要確定。這個工作將會話費(fèi)很多時間——幾秒鐘。 為了優(yōu)化這個過程,OS X和iOS上動態(tài)鏈接器使用了一個共享緩存,在/var/db/dyld/。對于每一種架構(gòu),操作系統(tǒng)有一個單獨(dú)的文件包含了絕大多數(shù)的動態(tài)鏈接庫,這些庫已經(jīng)互相連接并且符號都已經(jīng)確定。當(dāng)一個Mach-o文件被加載的時候,動態(tài)鏈接器會首先檢查共享緩存,如果存在相應(yīng)的庫就用。每一個進(jìn)程都把這個共享緩存映射到了自己的地址空間中。這個方法優(yōu)化了OS X和iOS上程序的加載時間。以下是讀取緩存的代碼:

    // load shared cache
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {

    mapSharedCache();
    }
3、實(shí)例化主程序

初始化一個ImageLoader類型的主程序sMainExecutable。后面的主程序相關(guān)的操作包括初始化都是通過這個對象來完成的。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、加載插入的動態(tài)庫

這里會遍歷所有插入的動態(tài)庫(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)過程如下:

5.1、主程序及其依賴的動態(tài)庫的Rebase
5.2、插入動態(tài)庫的Rebase
5.3、主程序及其依賴的動態(tài)庫的Binding
5.4、插入動態(tài)庫的Binding
5.5、弱綁定weakBind

備注:這里的recursiveBind和recursiveRebase都是遞歸的,所以會自底向上地分別調(diào)用 doRebase() 和 doBind() 方法,這樣被依賴的動態(tài)庫總是先于依賴它的動態(tài)庫執(zhí)行 rebase 和 binding。因為只有所依賴的子動態(tài)庫rebase和binding之后,符號地址才能被確定,這時候主動態(tài)庫再進(jìn)行binding就可以直接映射到相應(yīng)的子動態(tài)庫的符號地址。

  • 5.1、主程序及其依賴的動態(tài)庫的Rebase
    主程序調(diào)用link函數(shù)進(jìn)行鏈接。其中重定向rebase就是在這一步完成的,過程是link->recursiveRebaseWithAccounting->recursiveRebase。recursiveRebase函數(shù)會遞歸對主程序及其所依賴的庫和執(zhí)行文件進(jìn)行符號重定向(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ù)中并沒有調(diào)用recursiveBindWithAccounting函數(shù)。因為link函數(shù)內(nèi)部判斷l(xiāng)inkingMainExecutable為false才執(zhí)行recursiveBindWithAccounting,而此時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、插入動態(tài)庫的Rebase
    鏈接插入的動態(tài)庫,跟上一步一樣都是調(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、主程序及其依賴的動態(tài)庫的Binding
    調(diào)用函數(shù)recursiveBindWithAccounting會遞歸對主程序及其所依賴的動態(tài)庫進(jìn)行符號綁定(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、插入動態(tài)庫的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
    插入的動態(tài)庫鏈接完成后就執(zhí)行主程序的弱綁定,之后就會把狀態(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之后就開始了初始化工作。通過調(diào)用函數(shù) initializeMainExecutable進(jìn)行初始化:

     // run all initializers
     initializeMainExecutable(); 

主程序初始化初始是app啟動很重要的一個過程,在主程序初始化的過程中會同時初始化其所依賴的動態(tài)庫,這一過程完成了包括ObjC的初始化等工作。+load和C++構(gòu)造函數(shù)也是在這一過程中完成的。
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) 
              

主程序在初始化時是先遞歸調(diào)用其所依賴的動態(tài)庫完成初始化然后在完成自身初始化。在上面的函數(shù)流程中l(wèi)oad_images函數(shù)是在libobjc.A.dylib動態(tài)庫里面的,它是在程序初始化之前被注冊到dyld里面,等到初始化的時候執(zhí)行,完成libobjc的映射,非懶加載類就是在這一過程中被加載并調(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]);
}

通過源碼分析:首先是遍歷插入的動態(tài)庫進(jìn)行初始化,然后就是主程序的初始化。到這里程序的初始化就算完成了,這一階段其實(shí)就是完成了我們之前打印的ObjC setUpinitializers兩個階段。主程序初始化完成之后就進(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();

另外程序啟動過程中一些重要函數(shù)的解析

通過load_images作為符號斷點(diǎn)查看調(diào)用棧,可以得到更詳細(xì)的調(diào)用棧:

load_image斷點(diǎn).png

load_images調(diào)用棧.png

這個過程中涉及到幾個重要的動態(tài)庫和方法,主要有

動態(tài)庫:
libSystem.B.dylib,系統(tǒng)基礎(chǔ)動態(tài)庫,其他動態(tài)庫的初始化必須在他之后。其初始化函數(shù)是libSystem_initializer;
libdispatch.dylib,GCD所在的庫,其初始化函數(shù)是libdispatch_init;
libobjc.dylib,OC代碼庫,其初始化函數(shù)是_objc_init 。
幾個重要的函數(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)初始化,逼比如我們之前添加的打印程序啟動的環(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)行時的環(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會調(diào)用_objc_init(), 因此我們必須自己調(diào)用。
runtime_init() 類表allocatedClasses.init()和unattachedCategories.init(32)看似跟category相關(guān)的表,這個在后面類的
imp_implementationWithBlock_init(void)啟動回調(diào)機(jī)制。通常不會這么做。因為所有的初始話都是惰性的,但是對于某些進(jìn)程,我們會迫不及待地加載trampolines dylib。
_dyld_objc_notify_register向dyld注冊監(jiān)聽函數(shù)。以便在鏡像文件映射、取消映射和初始化objc時調(diào)用。Dyld將使用包含objc-image-info的鏡像文件的數(shù)組回調(diào)給map_images函數(shù)。
runtime_init() 運(yùn)行時環(huán)境初始化,里面主要是類表allocatedClasses 和unattachedCategories
exception_init()初始化ibobjc的異常處理系統(tǒng)

_dyld_objc_notify_register

函數(shù)_dyld_objc_notify_register是負(fù)責(zé)向dyld注冊相關(guān)的回調(diào)函數(shù),這些函數(shù)會在合適的時機(jī)dyld會通知調(diào)用,這函數(shù)就是它的三個參數(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會把這三個函數(shù)緩存起來,以方便在合適的時機(jī)調(diào)用它們。

map_images(ObjC setUp)

函數(shù)map_images是在libobjc庫中實(shí)現(xiàn)的,是在libobjc初始化的時候通過_dyld_objc_notify_register向dlyd注冊,用于監(jiān)聽鏡像文件的映射。OC相關(guān)的鏡像文件在完成rebase/binding之后,就會發(fā)出一個通知去調(diào)用map_images。但是有些時候鏡像文件是在libobjc初始化之前完成rebase/binding,這時候還沒有map_images這個函數(shù)。所以在libobjc初始化并調(diào)用 _dyld_objc_notify_register向dyld注冊的map_images時候,它會去把已經(jīng)完成rebase/binding的鏡像文件通過調(diào)用map_images進(jìn)行OC代碼相關(guān)的映射映射。所以map_images的調(diào)用是在rebase/binding之后,在動態(tài)庫(鏡像文件)調(diào)用initializer初始化之前的。函數(shù)map_images主要完成動態(tài)庫中的類的映射、初始化等操作。也就是我們前面打印的ObjC setUp階段。map_images通過鏡像文件的讀取類相關(guān)的信息進(jìn)行初始化,然后保存在類表中。
在完成類的映射,并完成非懶加載類的加載之后就可以調(diào)用OC相關(guān)的動態(tài)庫的初始化方法initializer了。initializer完成之后就會調(diào)用load_images函數(shù)。

load_images

load_images函數(shù)是在OC相關(guān)動態(tài)庫完成初始化之后調(diào)用的,在這期間,這里首先會加載非懶加載類的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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容