iOS進(jìn)階專項(xiàng)分析(八)、深入App啟動之dyld、map_images、load_images

今天我們先來看兩個經(jīng)典的面試題:

1、應(yīng)用程序啟動 在main函數(shù)之前都具體做了哪些內(nèi)容?

2、load在什么時(shí)候調(diào)用?子類、父類以及分類load的調(diào)用順序?

帶著這幾個問題我們開始本節(jié)的內(nèi)容:

  • 1、App編譯/啟動流程及動態(tài)鏈接器dyld
  • 2、map_images流程分析
  • 3、load_images流程分析
  • 4、面試題答案(僅供參考~)

一、App編譯流程及啟動流程dyld


下面圖示編譯流程

編譯流程.png

注意:只有靜態(tài)庫會在編譯階段會打包進(jìn)入可執(zhí)行文件,動態(tài)庫是在程序運(yùn)行時(shí)才會被加入可執(zhí)行文件。靜態(tài)庫與動態(tài)庫知識點(diǎn)深入傳送門

應(yīng)用程序啟動前,會先對代碼進(jìn)行編譯,在編譯階段會把靜態(tài)庫打包到可執(zhí)行性文件中,編譯完成后,進(jìn)入啟動階段,談及App啟動流程,肯定少不了我們的動態(tài)鏈接器dyld,整個啟動過程都是它在進(jìn)行協(xié)調(diào),dyld操作流程如下圖:

啟動流程dyld.png

從App啟動角度深入了解dyld

其實(shí)在App啟動過程中主要分為main函數(shù)之前和main函數(shù)之后,main函數(shù)之后就是從main函數(shù)到我們第一個視圖出現(xiàn)的這段時(shí)間。先來看一下main()之前主要做了哪些操作:

main()之前通過調(diào)用dyld對主程序運(yùn)行環(huán)境初始化,生成imageLoader把動態(tài)庫生成對應(yīng)的image鏡像文件,載入到內(nèi)存中,然后進(jìn)行鏈接綁定,接著初始化所有動態(tài)庫,在執(zhí)行所有插入的動態(tài)庫初始化的時(shí)候,同時(shí)也對load_images進(jìn)行了綁定。執(zhí)行初始化這個過程中,會優(yōu)先初始化系統(tǒng)庫libSystem,運(yùn)行起來Runtime,這個過程會進(jìn)入Runtime的入口函數(shù)_objc_init,接下來把之前鏈接的動態(tài)庫及符號都交給Runtime進(jìn)行map_imagesload_images操作,然后Runtime執(zhí)行完load_images之后會回調(diào)到dyld內(nèi)部,dyld收到信息回調(diào)后,最后查找main()函數(shù)的入口LC_MAIN,找到后就會調(diào)起我們的main()函數(shù),進(jìn)入我們開發(fā)者的代碼。

接下來結(jié)合文章開始的面試題,我們就來仔細(xì)分析一下Runtimemap_imagesload_images流程中間做了哪些操作?

二、map_images流程分析


Objc源碼下載地址

還是從系統(tǒng)庫libSystem的Runtime入口函數(shù)_objc_init開始分析:

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    
    //讀取影響運(yùn)行時(shí)的環(huán)境變量。
    environ_init();
    
    tls_init();
    
    //運(yùn)行C ++靜態(tài)構(gòu)造函數(shù)。libc在dyld調(diào)用我們的靜態(tài)構(gòu)造函數(shù)之前調(diào)用_objc_init()
    static_init();
    
    lock_init();
    //初始化libobjc的異常處理系統(tǒng)。由map_images()調(diào)用。
    exception_init();

    注冊回調(diào)函數(shù)
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

接下來我們開始進(jìn)入map_images:

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}


進(jìn)來后發(fā)現(xiàn)map_images直接返回了map_images_nolock

先來看一下map_images函數(shù)的注釋部分,我們得知:map_images主要作用就是處理由dyld映射的image(此處的image泛指二進(jìn)制可執(zhí)行程序)。

繼續(xù)點(diǎn)進(jìn)入map_images_nolock的實(shí)現(xiàn)部分, 我們來分析這里面主要做了什么:

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    定義一系列變量
    ......
    
    必要時(shí)執(zhí)行首次初始化
    
    //如果是第一次,就準(zhǔn)備初始化環(huán)境
    if (firstTime) {
        preopt_init();
    }

    // Find all images with Objective-C metadata.
    hCount = 0;

    計(jì)算class數(shù)量,根據(jù)總數(shù)調(diào)整各種表的大小。
    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];

            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable's size
#if __OBJC2__
                size_t count;
                _getObjc2SelectorRefs(hi, &count);
                selrefCount += count;
                _getObjc2MessageRefs(hi, &count);
                selrefCount += count;
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
                
#if SUPPORT_GC_COMPAT
                // Halt if this is a GC app.
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
#endif
            }
            
            hList[hCount++] = hi;
            
            if (PrintImages) {
                _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                             hi->fname(),
                             mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                             hi->info()->isReplacement() ? " (replacement)" : "",
                             hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                             hi->info()->optimizedByDyld()?" (preoptimized)":"");
            }
        }
    }

    ......
    
    執(zhí)行一次運(yùn)行時(shí)初始化,必須將其推遲到找到可執(zhí)行文件本身為止。 這需要在進(jìn)一步初始化之前完成。(如果可執(zhí)行文件不包含Objective-C代碼,但稍后會動態(tài)加載Objective-C,則該可執(zhí)行文件可能不會出現(xiàn)在此infoList中。
    
    if (firstTime) {
    
        //初始化sel方法表 并注冊系統(tǒng)內(nèi)部專門的方法。
        sel_init(selrefCount);
        arr_init();

#if SUPPORT_GC_COMPAT
        // Reject any GC images linked to the main executable.
        // We already rejected the app itself above.
        // Images loaded after launch will be rejected by dyld.

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE  &&  shouldRejectGCImage(mh)) {
                _objc_fatal_with_reason
                    (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                     OS_REASON_FLAG_CONSISTENT_FAILURE, 
                     "%s requires Objective-C garbage collection "
                     "which is no longer supported.", hi->fname());
            }
        }
#endif

#if TARGET_OS_OSX
        // Disable +initialize fork safety if the app is too old (< 10.13).
        // Disable +initialize fork safety if the app has a
        //   __DATA,__objc_fork_ok section.

        if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_13) {
            DisableInitializeForkSafety = true;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: disabling +initialize fork "
                             "safety enforcement because the app is "
                             "too old (SDK version " SDK_FORMAT ")",
                             FORMAT_SDK(dyld_get_program_sdk_version()));
            }
        }

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
                DisableInitializeForkSafety = true;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: disabling +initialize fork "
                                 "safety enforcement because the app has "
                                 "a __DATA,__objc_fork_ok section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
#endif

    }

    直接開始image讀取
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
}


對源碼進(jìn)行分析, map_images_nolock 方法的流程如下:

1、 判斷是不是第一次,如果是第一次,那么就開始準(zhǔn)備初始化環(huán)境

    if (firstTime) {
        preopt_init();
    }

2、計(jì)算class數(shù)量,根據(jù)總數(shù)調(diào)整各種表的大小(這個步驟里面會判斷GC(Garbage Collection),因?yàn)镺bjective-C之前是做了垃圾回收機(jī)制兼容的,現(xiàn)在則不支持了。盡管目前不支持GC了,但是蘋果并沒有刪除這些兼容性代碼)

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
        
            調(diào)整表的大小部分操作
            ......
            
            
            GC相關(guān)邏輯判斷            
#if SUPPORT_GC_COMPAT

                // Halt if this is a GC app.
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
#endif
            }
            
            hList[hCount++] = hi;
            
        }
    }


3、判斷是不是首次執(zhí)行,如果是,會初始化各種表

if (firstTime) {
        sel_init(selrefCount);
        arr_init();
        
        ......
        
        繼續(xù)邏輯判斷GC相關(guān)
        
        #if SUPPORT_GC_COMPAT
        ......
        #endif
       
}



4、接著開始讀取images,并將firstTime置為 NO

//判斷,然后進(jìn)行images讀取

if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

//將firstTime置為NO,下次就不重新創(chuàng)建表了
firstTime = NO;

總結(jié)map_images_nolock的流程就是:

  1. 判斷firstTimefirstTime為YES,則執(zhí)行環(huán)境初始化的準(zhǔn)備,為NO就不執(zhí)行
  2. 計(jì)算class數(shù)量,根據(jù)總數(shù)調(diào)整各種表的大小并做了GC相關(guān)邏輯處理(不支持GC則打印提示信息)
  3. 判斷firstTimefirstTime為YES,執(zhí)行各種表初始化操作,為NO則不執(zhí)行
  4. 執(zhí)行_read_images進(jìn)行讀取,然后將firstTime置為NO,就不再進(jìn)入上面的邏輯了,下次進(jìn)入map_images_nolock就開始直接_read_images

接下來我們重點(diǎn)分析_read_images底層實(shí)現(xiàn),看看到底做了哪些主要操作,進(jìn)入源碼實(shí)現(xiàn)如下:

/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked 
* list beginning with headerList. 
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{

    定義一系列局部變量
    ......

    1. 重新初始化TaggedPointer環(huán)境****************
    
    if (!doneOnce) {
        doneOnce = YES;

         ......

        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        
        initializeTaggedPointerObfuscator();

        ......
        
        注意!!!!!創(chuàng)建表gdb_objc_realized_classes和allocatedClasses
        
        ......
        
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
        
        ......
    }


    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    
    2. 開始遍歷頭文件,進(jìn)行類與元類的讀取操作并標(biāo)記(舊類改動后會生成新的類,并重映射到新的類上)************************

    for (EACH_HEADER) {
        //從頭文件中拿到類的信息
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        
        if (! mustReadClasses(hi)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->isPreoptimized();

        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i];
            
            //!!!!!!核心操作,readClass讀取類的信息及類的更新
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
            
            ......
            
        }
    }
        
    ......

    3. 讀取@selector*************************************
    
    // Fix up @selector references
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->isPreoptimized()) continue;
            
            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                sels[i] = sel_registerNameNoLock(name, isBundle);
            }
        }
    }


    ......
    
    
    4. 讀取協(xié)議protocol*************************************
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();

        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

   ......


    5. 處理分類category,并rebuild重建這個類的方法列表method list*******************************
    
    // Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            
            ......
            
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

    ......

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }


    最后是一堆打印***********
    ......
   
}


_read_images的實(shí)現(xiàn)主要分為以下步驟:

  1. 重新初始化TaggedPointer環(huán)境
  2. 開始遍歷頭文件,進(jìn)行類與元類的讀取操作并標(biāo)記(舊類改動后會生成新的類,并重映射到新的類上)
  3. 讀取@selector方法
  4. 讀取協(xié)議protocol
  5. 處理分類category,并rebuild重建這個類的方法列表method list

既然是讀取類,類的結(jié)構(gòu)中包含類本身以及類的所有信息(例如分類,方法,協(xié)議)。

下面我們就針對這些我們想知道的內(nèi)容進(jìn)行分析:類與元類的讀取、方法@selector的讀取、協(xié)議protocol的讀取以及分類category的讀取

第1步、重新初始化TaggedPointer環(huán)境

判斷doneOnce,如果doneOnce為NO,則首先重置及初始化taggedPointer,然后創(chuàng)建兩個表,一個叫gdb_objc_realized_classes用來存放已命名的類的列表,另一個叫allocatedClasses用來存放已分配的所有類(和元類)


if (!doneOnce) {
        doneOnce = YES;//這個邏輯只執(zhí)行一次
        
        //重置及初始化TaggedPointer環(huán)境
        
        if (DisableTaggedPointers) {
            disableTaggedPointers();
            }
        initializeTaggedPointerObfuscator();
        
        
        //創(chuàng)建表gdb_objc_realized_classes和allocatedClasses
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
        
    }

分別點(diǎn)到這兩個表的定義部分,根據(jù)注釋能查看出這兩個表大概的作用

// This is a misnomer: gdb_objc_realized_classes is actually a list of 
// named classes not in the dyld shared cache, whether realized or not.
gdb_objc_realized_classes實(shí)際上是不在dyld共享緩存中的已命名類的列表,無論是否實(shí)現(xiàn)

NXMapTable *gdb_objc_realized_classes;  // exported for debuggers in objc-gdb.h

/***********************************************************************
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
**********************************************************************/

static NXHashTable *allocatedClasses = nil;

這里拓展一下這兩張表的類型:gdb_objc_realized_classes的類型是NXMapTableallocatedClasses表的類型是NXHashTable

可以簡單理解NSHashTable、NSMapTable分別對應(yīng)的是我們常用的NSSet和NSDictionary,并且額外提供了weak指針來使用垃圾回收機(jī)制。

NSDictionary底層實(shí)現(xiàn)也是使用了NSMapTable(散列表),(備注:蘋果官網(wǎng)并沒有這些類的實(shí)現(xiàn),想要查看NSDictionary和NSArray的實(shí)現(xiàn)源碼可以去GNUstep官網(wǎng)下載或者百度網(wǎng)盤下載)

使用NSMapTable是因?yàn)樗鼜?qiáng)大NSMapTable相對于NSDictionary的優(yōu)勢

第2步、類與元類的讀取

遍歷頭文件,進(jìn)行類與元類的讀取操作,讀取完后標(biāo)記


// Discover classes. Fix up unresolved future classes. Mark bundle classes.

    for (EACH_HEADER) {
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        
        if (! mustReadClasses(hi)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->isPreoptimized();

        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }

其中主要做了readClass來讀取編譯器編寫的類和元類,我們重點(diǎn)來仔細(xì)分析一下類的讀取過程,進(jìn)入readClass源碼

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();
    
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }
    
    ......

    Class replacing = nil;
    if (Class newCls = popFutureNamedClass(mangledName)) {
    
        ......
                
        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro;
        memcpy(newCls, cls, sizeof(objc_class));
        rw->ro = (class_ro_t *)newCls->data();
        newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
    
    if (headerIsPreoptimized  &&  !replacing) {
    
        ......
        
        assert(getClass(mangledName));
    } else {
        addNamedClass(cls, mangledName, replacing);
        addClassTableEntry(cls);
    }

    ......
    
    return cls;
}


從源碼中可以看出,readClass方法有返回值,并且包含三種邏輯處理:

  1. 找不到該類的父類,可能是弱綁定,直接返回nil;
  2. 找到類了,判斷這個類是否是一個future的類(可以理解為需要實(shí)現(xiàn)的一個類,也可以理解為這個類是否有變化),如果有變化則創(chuàng)建新類,并把舊類的數(shù)據(jù)拷貝一份然后賦值給新類newCls,然后調(diào)用addRemappedClass進(jìn)行重映射,用新的類替換掉舊的類,并返回新類newCls的地址
  3. 找到類了,如果類沒有任何變化,則不進(jìn)行任何操作,直接返回class

readClass的底層實(shí)現(xiàn)部分做個延伸思考:日常開發(fā)中,對于已經(jīng)啟動完成的工程項(xiàng)目,如果我們未修改任何類的數(shù)據(jù),那么再次點(diǎn)擊運(yùn)行會很快完成,但是一旦我們在對這些類進(jìn)行修改后,在讀取這些類的信息(包括類本身的信息以及下面我們要繼續(xù)分析的協(xié)議protocol、分類category、方法selector),就需要對該類的數(shù)據(jù)進(jìn)行更新,這個更新實(shí)際上是新建一個類,然后拷貝舊類的數(shù)據(jù)賦值給新類,然后重映射并用新類替換掉新類,這里面的拷貝以及讀寫過程其實(shí)是相當(dāng)耗時(shí)的!這是類信息改動之后項(xiàng)目再次Run運(yùn)行起來會比較慢的原因之一。

繼續(xù)分析,既然做了類信息的讀取,那么讀取到的數(shù)據(jù)到底存在哪里呢?在readClass源碼最后部分找到這兩句代碼

addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
        

先看這兩句的第一句代碼做了什么:進(jìn)入addNameClass找到

NXMapInsert(gdb_objc_realized_classes, name, cls);

發(fā)現(xiàn)已經(jīng)讀取完成的類,會被存放到了這個表gdb_objc_realized_classes里面!

然后繼續(xù)看第二句里面做了啥:

static void addClassTableEntry(Class cls, bool addMeta = true) {
    runtimeLock.assertLocked();
    
    ......

    if (!isKnownClass(cls))
        NXHashInsert(allocatedClasses, cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

分析源碼注釋及源碼得出,addClassTableEntry里面會把這個讀取完成的類直接先添加到allocatedClasses表里面,然后再判斷addMeta是否為YES,然后會把當(dāng)前這個類的元類metaClass也添加到allocatedClasses這個表里面。

這里是一個遞歸的邏輯,我們需要來分析一下:由于我們上一步是這樣直接調(diào)用的:

addClassTableEntry(cls);

所以進(jìn)入這個方法的時(shí)候,只傳入了一個cls并沒有傳入addMeta,所以這里addMeta默認(rèn)就是YES,然后繼續(xù)遞歸調(diào)用當(dāng)前addClassTableEntry,注意:第二次遞歸調(diào)用的時(shí)候,addMeta傳入的是false,所以第二次就不會再添加元類了,這里的邏輯主要是保證元類只添加一次。所以addClassTableEntry里面其實(shí)是做了把類和元類都加到allocatedClasses表里面。

到此為止,類和元類的讀取我們已經(jīng)明白了,下面用同樣的分析,分析類的方法、協(xié)議以及分類

第3步、方法@selector的讀取

接下來進(jìn)到第四部的代碼部分

// Fix up @selector references
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->isPreoptimized()) continue;
            
            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                sels[i] = sel_registerNameNoLock(name, isBundle);
            }
        }
    }

點(diǎn)擊sel_registerNameNoLock,找到__sel_registerName,在它里面找到關(guān)鍵代碼

if (!namedSelectors) {
        namedSelectors = NXCreateMapTable(NXStrValueMapPrototype, 
                                          (unsigned)SelrefCount);
    }
    if (!result) {
        result = sel_alloc(name, copy);
        // fixme choose a better container (hash not map for starters)
        NXMapInsert(namedSelectors, sel_getName(result), result);
    }

邏輯其實(shí)就是:把方法名插入并存儲到namedSelectors這個表里面.

第4步,協(xié)議protocol的讀取
// Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        創(chuàng)建表protocol_map
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();

        拿到頭文件中協(xié)議列表
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
        讀取protocol
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

找到關(guān)鍵函數(shù)readProtocol,進(jìn)入發(fā)現(xiàn)其實(shí)讀取protocol的操作是把protocol存進(jìn)協(xié)議表protocol_map

insertFn(protocol_map, installedproto->mangledName, installedproto);

第5步,分類category的讀取

來看看分類部分的邏輯

// Discover categories. 
    for (EACH_HEADER) {
    從頭文件中找到所有分類
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            根據(jù)分類,獲取分類對應(yīng)的類
            if (!cls) {
            如果分類所屬的類找不到,那么就會把這個這個分類category_t置為nil
                ......
                
                catlist[i] = nil;
                
                ......
                
                continue;
            }

            ......
            
            如果分類所屬的類找到了,那么判斷這個分類里面的實(shí)例方法instanceMethods,協(xié)議protocols,以及屬性instanceProperties是否存在,如果存在,就把這些方法分別同步更新到對應(yīng)的類和元類中
            
            bool classExists = NO;
            
            //把分類新增的方法、協(xié)議、屬性都添加到類中
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                
                ......
                
             }

            //把分類新增的方法、協(xié)議、屬性都添加到元類中
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                
                ......
                
            }
        }
    }

總結(jié)一下分類category的讀取,里面主要做了下面這些步驟:

  1. 從頭文件中獲取所有的分類列表catlist,然后循環(huán)遍歷這個列表
  2. 在循環(huán)中,判斷當(dāng)前分類cat所屬的類是否存在,如果不存在則把這個分類置為空catlist[i] = nil; 如果這個分類所屬的類存在,那么開始下面兩個步驟:
  3. 第一個步驟:判斷這個分類cat中是否有實(shí)例方法instanceMethods,協(xié)議protocols以及屬性實(shí)例instanceProperties,如果有,那么進(jìn)入remethodizeClass,重新rebuild當(dāng)前類cls的方法列表
  4. 第二個步驟:繼續(xù)判斷這個分類cat中是否有類方法classMethods,協(xié)議protocols以及類屬性_classProperties,然后重新rebuild當(dāng)前類所對應(yīng)元類cls->ISA()的方法列表。

注意第一步和第二步這兩個方法分別對應(yīng)處理的是分類的類和分類的類對應(yīng)的元類。處理類調(diào)用的是remethodizeClass(cls);,而處理元類調(diào)用的是remethodizeClass(cls->ISA())

接下來我們進(jìn)入remethodizeClass方法實(shí)現(xiàn)部分繼續(xù)深究這個方法實(shí)現(xiàn)步驟:

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
    
        ......
        
        attachCategories(cls, cats, true /*flush caches*/);  
              
        free(cats);
    }
}

找到關(guān)鍵實(shí)現(xiàn)attachCategories函數(shù),進(jìn)入

將方法列表以及屬性和協(xié)議從類別附加到類。
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}


注意,這里面的類型和Class類中的類型是完全一致并且對應(yīng)的,下面貼上Class的class_rw_t結(jié)構(gòu)做個對比

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    
    ......
    
}

注意類結(jié)構(gòu)中方法表、屬性表、協(xié)議表的類型分別是method_array_tproperty_array_tprotocol_array_t,而這個三個表底層都是由list_array_tt進(jìn)行實(shí)現(xiàn)的,只不過里面存儲的數(shù)據(jù)類型不相同,這個三個表里面分別對應(yīng)存儲的是method_list_tproperty_list_t以及protocol_list_t類型的數(shù)據(jù)

class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
{
    typedef list_array_tt<method_t, method_list_t> Super;

 public:
    method_list_t **beginCategoryMethodLists() {
        return beginLists();
    }
    
    method_list_t **endCategoryMethodLists(Class cls);

    method_array_t duplicate() {
        return Super::duplicate<method_array_t>();
    }
};

class property_array_t : 
    public list_array_tt<property_t, property_list_t> 
{
    typedef list_array_tt<property_t, property_list_t> Super;

 public:
    property_array_t duplicate() {
        return Super::duplicate<property_array_t>();
    }
};


class protocol_array_t : 
    public list_array_tt<protocol_ref_t, protocol_list_t> 
{
    typedef list_array_tt<protocol_ref_t, protocol_list_t> Super;

 public:
    protocol_array_t duplicate() {
        return Super::duplicate<protocol_array_t>();
    }
};


并且list_array_tt在設(shè)計(jì)的時(shí)候,提供了attachList方法,可以調(diào)用這個方法往表里繼續(xù)添加數(shù)據(jù)

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

所以,通過結(jié)合objc_class源碼,來對比分析attachCategories源碼,我們能夠明白attachCategories函數(shù)里面主要做了下面的操作:

  1. 對應(yīng)Class的結(jié)構(gòu),新建方法表method_list_t **mlistsproperty_list_t **proplistsprotocol_list_t **protolists
  2. 根據(jù)當(dāng)前類cls分類的數(shù)量,進(jìn)行while循環(huán),把分類里面包含的方法,協(xié)議,屬性都加到上面的三個表中
  3. 獲取當(dāng)前類的rw,通過rw拿到對應(yīng)的methodspropertiesprotocols,然后由于這三個表都是由list_array_tt實(shí)現(xiàn),直接調(diào)用list_array_ttattachLists方法,把category分類里面的方法,協(xié)議,屬性都添加到當(dāng)前類的表里面去。

到此,map_images的主要操作都已經(jīng)分析完成,下面總結(jié)一下map_images的主要流程及流程圖。

流程如下:

  1. 初始化環(huán)境TaggedPointer環(huán)境,同時(shí)新建兩個表:gdb_objc_realized_classes用來存儲讀取完成的類的類名,allocatedClasses存儲已經(jīng)創(chuàng)建的類及元類,接下來作為類是否創(chuàng)建的邏輯判斷
  2. 讀取類read_class,如果是需要實(shí)現(xiàn)的新類,那么進(jìn)行實(shí)現(xiàn)并重映射,并用新類的地址替換舊類的地址,然后把實(shí)現(xiàn)的類的類名存儲到表gdb_objc_realized_classes中,同時(shí)順帶把這個類以及元類都保存到表allocatedClasses中做了關(guān)聯(lián)綁定,方便后續(xù)邏輯處理
  3. 讀取類的方法@selector,調(diào)用sel_registerNameNoLock,把方法名存儲到表namedSelectors
  4. 讀取類的protocol協(xié)議,調(diào)用readProtocol,把協(xié)議對象protocol_tmangledName存儲到表protocol_map中。
  5. 最后讀取類的分類category,category對應(yīng)兩個邏輯分別調(diào)用remethodizeClass,這兩個邏輯分別是:實(shí)例方法/屬性/協(xié)議添加到當(dāng)前類,而類方法/屬性/協(xié)議添加給當(dāng)前類對應(yīng)的元類,因?yàn)轭惙椒ū旧砭褪鞘谴鎯υ谠愔械摹>唧w操作就是先獲取到所有分類及中的數(shù)據(jù),添加到新的數(shù)組中,然后直接調(diào)用rw->methods.attachLists/rw->properties.attachLists/rw->protocols.attachLists,利用list_array_tt中的attachLists方法,把這些分類,協(xié)議,屬性都添加到類和元類的rw數(shù)據(jù)中

流程圖如下:

map_images.png

三、load_images流程分析


接下來我們分析load_images底層的邏輯流程,點(diǎn)擊load_images進(jìn)入

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // 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();
}

找到關(guān)鍵代碼

  1. prepare_load_methods
  2. call_load_methods

開始分析

1、prepare_load_methods底層實(shí)現(xiàn)

貼上prepare_load_methods源碼

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    //先遞歸調(diào)度 類和父類
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    
    //再調(diào)度分類
    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

進(jìn)入schedule_class_load,這個函數(shù)底層如下

static void schedule_class_load(Class cls)
{
    ......
    
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    
    ......
}

這里面添加的方法add_class_to_loadable_list的底層實(shí)現(xiàn)如下

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();
    取到load方法
    method = cls->getLoadMethod();
    
    ......
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}


我們發(fā)現(xiàn)這個添加過程實(shí)際上就是把loadable_class類型的結(jié)構(gòu)體,存儲到表待調(diào)度load的這張表loadable_classes中,而存儲的結(jié)構(gòu)體loadable_class類型包含類名cls以及該類的load方法IMP

struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};

cls->getLoadMethod()方法得到的就是該類的Load方法

IMP 
objc_class::getLoadMethod()
{
    ......

    mlist = ISA()->data()->ro->baseMethods();
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            if (0 == strcmp(name, "load")) {
                return meth.imp;
            }
        }
    }

    return nil;
}

schedule_class_load底層實(shí)現(xiàn)原理是:獲取父類,然后繼續(xù)遞歸調(diào)用schedule_class_load,然后把這些類按父類的父類->父類->子類這個順序把類和類的load方法添加到loadable_classes表中。這也是為什么類的+(load)方法執(zhí)行順序是從父類到子類的。

在調(diào)用schedule_class_load添加完成類之后,又繼續(xù)處理分類,分類內(nèi)部調(diào)用_category_getLoadMethod拿到分類中重寫的load方法,然后也調(diào)用add_category_to_loadable_list把分類cat和分類的load方法添加到表loadable_categories中。

所以總結(jié)prepare_load_methods準(zhǔn)備load方法的邏輯就是:

  1. 先處理類:遞歸按照先父類再子類的順序,把類和類的load方法整合成一個結(jié)構(gòu)體對象loadable_class,然后把這個結(jié)構(gòu)體對象存到表loadable_classes中。
  2. 處理完成類之后,再開始處理分類:獲取分類的load方法,把分類和分類的load方法整合成一個結(jié)構(gòu)體對象loadable_category然后存儲到表loadable_categories中。

到這里,load方法準(zhǔn)備工作完畢。

2、call_load_methods底層實(shí)現(xiàn)

接下來進(jìn)入重點(diǎn),load方法的調(diào)用部分

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;
}

先觀察這個函數(shù)實(shí)現(xiàn)部分,發(fā)現(xiàn)這個do—while循環(huán)被包含在objc_autoreleasePoolPush()objc_autoreleasePoolPop中,蘋果使用了autoreleasePool是為了節(jié)省內(nèi)存開銷。

然后我們繼續(xù)來看循環(huán)體部分:

do {
        //1、while循環(huán)調(diào)用call_class_loads()方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        //2、調(diào)用call_category_loads()并返回一個bool布爾值并賦值給more_categories
        more_categories = call_category_loads();

    } while (loadable_classes_used > 0  ||  more_categories);

接下來我們繼續(xù)分析call_class_loadscall_category_loads底層實(shí)現(xiàn)。

先來看看調(diào)用類的load函數(shù)call_class_loads

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

簡化源碼如下

static void call_class_loads(void)
{
    ......
    
    for (i = 0; i < loadable_classes_used; i++) {
        Class cls = loadable_classes[i].cls;
        load_method_t load_method = (load_method_t) loadable_classes[i].method;

        ......
        
        (*load_method)(cls, SEL_load);
    }
    ......
}

這個過程其實(shí)就是從之前存儲好的表loadable_classes中取出Class和對應(yīng)load方法的load_method_t對象,直接調(diào)用。

然后看看調(diào)用分類的load函數(shù)call_category_loads

static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
        
            ......
            
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }

    ......
    
    return new_categories_added;
}

這個過程和類的邏輯基本一致,也是就是從之前存儲好的表loadable_categories中取出分類Category和對應(yīng)load方法的load_method_t對象,然后通過_category_getClass獲取到分類對應(yīng)的類,然后用類直接調(diào)用load方法。

到此為止,load_images主要流程也已經(jīng)分析完畢。load_images主要做了下面這些步驟:

第一步.準(zhǔn)備load方法:prepare_load_methods

以先處理類,后處理分類 以及 先處理父類,后處理子類的順序存儲到待調(diào)度的表中。

類的處理邏輯:把類對象Class和類對應(yīng)的load方法的IMP整合成一個loadable_class類型的結(jié)構(gòu)體對象存儲在表loadable_classes中。

分類的處理邏輯:把分類對象Category和對應(yīng)的load方法IMP整合成一個loadable_category類型的結(jié)構(gòu)體對象存儲在表loadable_categories中。

第二步.調(diào)用load方法:call_load_methods

以先調(diào)用類Class的load,后處理分類Category,通過分類找到對應(yīng)的類,然后由類調(diào)用load方法的順序進(jìn)行處理

這個調(diào)用處理的順序是根據(jù)準(zhǔn)備方法prepare_load_methods中準(zhǔn)備好的兩張表loadable_classesloadable_categories的順序而來的。調(diào)用完就從表中移除,全部調(diào)用完結(jié)束循環(huán)。

下面是我對load_images方法實(shí)現(xiàn)邏輯的的流程圖:

load_images.png

四、面試題答案(僅供參考~)


1、應(yīng)用程序啟動 在main函數(shù)之前都具體做了哪些內(nèi)容?

程序啟動時(shí),系統(tǒng)XNU執(zhí)行程序的可執(zhí)行二進(jìn)制文件,從內(nèi)核態(tài)切換到用戶態(tài),根據(jù)路徑找到并運(yùn)行動態(tài)鏈接器dyld,并把控制權(quán)交給dyld,然后啟動dyld進(jìn)行程序環(huán)境初始化,然后讀取可執(zhí)行文件Mach-O,開始根據(jù)頭文件內(nèi)容讀取動態(tài)庫并初始化主程序,初始化主程序后,就開始鏈接讀取完成的動態(tài)庫到主程序可執(zhí)行文件中,然后初始化動態(tài)庫。在初始化其他動態(tài)庫之前,會最先初始化系統(tǒng)庫libsystem,運(yùn)行Runtime。系統(tǒng)庫libsystem初始化完成后,就會初始化其他動態(tài)庫,然后由Runtime調(diào)用map_images來讀取類、方法、協(xié)議以及分類并存儲到對應(yīng)的表中(注意:分類并不是直接存,而是通過attachLists方法把分類的數(shù)據(jù)添加到類里面),然后Runtime會繼續(xù)調(diào)用load_images調(diào)用所有類的load方法以及分類的load方法,這些都做完之后,通過dyld提供的回調(diào)_dyld_objc_notify_register,告訴dyld加載完畢,然后dyld就開始找主程序的入口main函數(shù),最后進(jìn)入程序的main函數(shù)。

2、load在什么時(shí)候調(diào)用?子類、父類以及分類load的調(diào)用順序?

load方法調(diào)用是在應(yīng)用程序main函數(shù)之前,應(yīng)用啟動時(shí)dyld處理完image鏡像文件,通過回調(diào)傳給runtime,交由runtimeload_images方法中調(diào)用的。

load方法調(diào)用順序?yàn)椋合忍幚眍悾筇幚矸诸悾惶幚眍惖捻樞蚴窍雀割悾笞宇?/p>

在調(diào)用類的load方法時(shí),做了遞歸處理,會先調(diào)用父類的load,然后再調(diào)用子類的load,所有類的load方法調(diào)用完成后,才會開始處理所有類的分類,分類的處理順序取決于Mach-O頭文件,和類的順序沒有直接關(guān)系。先后順序即:父類->子類->所有類的分類

驗(yàn)證方式:實(shí)現(xiàn)子類和父類,重寫load方法,在其中進(jìn)行NSLog打印便可以看出,這里我就不驗(yàn)證了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,908評論 2 372