Runtime源代碼解讀8(應(yīng)用載入)

在 Clang 編譯 Objective-C 源文件時(shí),需要先將 Objective-C 代碼轉(zhuǎn)化為 C 語(yǔ)言代碼,然后編譯得到目標(biāo)文件(object),最后將目標(biāo)文件鏈接為二進(jìn)制文件(binary)。二進(jìn)制文件表現(xiàn)為應(yīng)用或框架、類庫(kù)等。操作系統(tǒng)通過dyld提供的 API 啟動(dòng)或加載二進(jìn)制文件。大致過程是:首先通過open(...)函數(shù)打開二進(jìn)制文件;然后通過mmap(...)內(nèi)存映射函數(shù),將二進(jìn)制文件中的目標(biāo)文件映射到內(nèi)存空間,因此目標(biāo)文件也可以稱為鏡像文件,鏡像文件所映射的內(nèi)存區(qū)域則為鏡像(image);最后進(jìn)行鏡像綁定、依賴初始化等其他操作。

本文的主要內(nèi)容是:探討 runtime 如何讀取鏡像中定義的 Objective-C 元數(shù)據(jù)。

注意:工程編譯生成的動(dòng)態(tài)鏈接庫(kù)(Overview of Dynamic Libraries、深入剖析iOS動(dòng)態(tài)鏈接庫(kù))、靜態(tài)鏈接庫(kù)、可執(zhí)行程序統(tǒng)稱為二進(jìn)制文件(binary)。

一、Objective-C 體系初始化

面向?qū)ο蟮?Objective-C 體系的總加載入口在objc-os.mm文件中的void _objc_init(void)。操作系統(tǒng)執(zhí)行了_objc_init()后,才能開始加載 Objective-C 編寫的應(yīng)用。

void _objc_init(void)用于初始化 runtime 環(huán)境,并注冊(cè)三個(gè)回調(diào) 以監(jiān)聽dyld加載鏡像的兩個(gè)狀態(tài)、以及卸載鏡像動(dòng)作。開頭的三行代碼說明該函數(shù)只被執(zhí)行一次。操作系統(tǒng)調(diào)用dyld的 API 加載(dyld開源代碼)二進(jìn)制文件并完成內(nèi)存映射后,一旦鏡像切換到指定狀態(tài) 或者 監(jiān)聽到鏡像卸載動(dòng)作,則會(huì)觸發(fā)_objc_init中所注冊(cè)的相應(yīng)的回調(diào)函數(shù):

  • 鏡像切換到dyld_image_state_bound(鏡像完成綁定)時(shí),觸發(fā)map_images(...)回調(diào)函數(shù),將鏡像中定義的 Objective-C 元數(shù)據(jù)(類、分類、協(xié)議等等)加載到 runtime;

  • 鏡像加載切換到dyld_image_state_dependents_initialized(鏡像的依賴庫(kù)完成初始化)時(shí),觸發(fā)load_images (...)回調(diào)函數(shù),執(zhí)行鏡像中的 Objective-C 元素初始化操作,主要是執(zhí)行類和分類的load方法;

  • 監(jiān)測(cè)到卸載鏡像動(dòng)作時(shí),觸發(fā)unmap_image (...)回調(diào)函數(shù),將屬于該鏡像的元素從 runtime 系統(tǒng)記錄的已加載 Objective-C 元素中移除。

// 運(yùn)行時(shí)環(huán)境的啟動(dòng)總?cè)肟?void _objc_init(void)
{
    // 重要:_objc_init全局只調(diào)用一次
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // 運(yùn)行環(huán)境初始化系列操作
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();
    
    // 監(jiān)聽鏡像加載卸載,注冊(cè)回調(diào)    
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

二、載入鏡像

載入鏡像是由系統(tǒng)或應(yīng)用調(diào)用dyld的 API 實(shí)現(xiàn)的,例如用戶打開 APP 則會(huì)觸發(fā)載入鏡像過程。鏡像載入過程需要完成目標(biāo)文件映射到內(nèi)存空間、依賴庫(kù)初始化等工作。而本章僅介紹內(nèi)存映射過程生成的鏡像的內(nèi)存布局。使用 Clang 編譯.m源文件,所生成的.o目標(biāo)文件包含以下三大塊信息,依次相鄰分布:

  • 鏡像元數(shù)據(jù)區(qū):包含頭信息(header info)、架構(gòu)相關(guān)信息fat_header(針對(duì)多架構(gòu)編譯的目標(biāo)文件才有);
  • 加載命令區(qū):加載命令(load commands),包含了鏡像中保存的數(shù)據(jù)的元數(shù)據(jù),加載命令用于訪問或操作目標(biāo)文件中的數(shù)據(jù)。例如LC_SEGMENT_64用于讀取目標(biāo)數(shù)據(jù)段中保存的數(shù)據(jù);
  • 數(shù)據(jù)區(qū):包括代碼段(text segment)、數(shù)據(jù)段(data segment)、符號(hào)表(symbol table)等等。其中,代碼段顧名思義用于保存代碼,目標(biāo)文件中保存的代碼是機(jī)器碼。數(shù)據(jù)段用于保存 在代碼中初始化的全局靜態(tài)變量及全局變量,這些數(shù)據(jù)固化在鏡像文件中。

下圖是一個(gè).o目標(biāo)文件的結(jié)構(gòu)示例:

目標(biāo)文件實(shí)例.png

注意:關(guān)于目標(biāo)文件的結(jié)構(gòu)的詳細(xì)介紹,可以參考 認(rèn)識(shí)MachO。

2.1 鏡像的內(nèi)存結(jié)構(gòu)

目標(biāo)文件映射到內(nèi)存生成鏡像,由于操作系統(tǒng)的 CPU 架構(gòu)通常是單一的,因此鏡像基本只包含單一架構(gòu)的數(shù)據(jù)。以下是目標(biāo)文件映射到內(nèi)存生成的鏡像的內(nèi)存結(jié)構(gòu)示意圖。圖中左邊表示鏡像內(nèi)存的布局結(jié)構(gòu),從下至上為低地址到高地址。右邊的mach_header_64結(jié)構(gòu)體定義了鏡像頭信息的數(shù)據(jù)結(jié)構(gòu);load_command結(jié)構(gòu)體表示抽象的加載命令類型,通常鏡像中不會(huì)直接保存load_command,而是繼承了load_command的具體加載命令類型,例如:segment_command_64用于加載鏡像中 segment 的數(shù)據(jù);section_64結(jié)構(gòu)體用于記錄 segment 中具有特定含義的一塊數(shù)據(jù)(section)的元信息。

  • 紅色區(qū)域表示保存mach_header結(jié)構(gòu)體;
  • 綠色區(qū)域表示保存load_command結(jié)構(gòu)體,加載數(shù)據(jù)段;
  • 黃色區(qū)域表示保存section_64結(jié)構(gòu)體;
  • 紫色區(qū)域表示保存了一塊代碼段數(shù)據(jù)(section);
  • 洋紅色區(qū)域表示保存了一塊常量數(shù)據(jù)段數(shù)據(jù)(section);
  • 青色區(qū)域保存保存了一塊數(shù)據(jù)段數(shù)據(jù)(section);
鏡像內(nèi)存結(jié)構(gòu)示意圖.jpg

2.1 讀取鏡像中的數(shù)據(jù)

上圖中segment_command_64中紅色標(biāo)記的成員,以及section_64中綠色標(biāo)記的成員,它們是讀取鏡像中數(shù)據(jù)的關(guān)鍵。

segment_command_64結(jié)構(gòu)體中:

  • cmdsize:具體的加載命令結(jié)構(gòu)體類型 所占用字節(jié)數(shù);
  • fileoff:數(shù)據(jù)段(segment)的數(shù)據(jù)區(qū)在鏡像內(nèi)存空間數(shù)據(jù)區(qū)中的相對(duì)地址;
  • filesize:數(shù)據(jù)段(segment)的數(shù)據(jù)區(qū)占用字節(jié)數(shù);
  • nsect:表示數(shù)據(jù)段中包含的 data section 的數(shù)量;

section_64結(jié)構(gòu)體中:

  • size:數(shù)據(jù)段 data section 占用字節(jié)數(shù);
  • offset:數(shù)據(jù)段 data section 在鏡像內(nèi)存空間數(shù)據(jù)區(qū)中的相對(duì)地址。

讀取鏡像的數(shù)據(jù)段(segment)的代碼如下。其中head參數(shù),指向鏡像的頭信息結(jié)構(gòu)體,即指向鏡像文件映射的內(nèi)存緩沖區(qū),segname為數(shù)據(jù)段名稱。其原理是:

  • 首先通過頭信息內(nèi)存地址 + sizeof(struct mach_header_64)獲取加載命令區(qū)的起始地址;
  • 然后遍歷加載命令區(qū)中所有的加載命令,當(dāng)遍歷到的加載命令sgpLC_SEGMENT_64,且加載命令的數(shù)據(jù)段名稱segname匹配時(shí),返回該數(shù)據(jù)段,反之則通過sgp = sgp->cmdsize跳過命令占用空間以遍歷到下一個(gè)加載命令。
static const segment_command_64 *
getsegbynamefromheader(const mach_header_64 *head, const char *segname)
{
    const segment_command_64 *sgp;
    unsigned long I;
    
    sgp = (const segment_command_64 *) (head + 1);
    for (i = 0; i < head->ncmds; i++){
        if (sgp->cmd == LC_SEGMENT_64) {
            if (strncmp(sgp->segname, segname, sizeof(sgp->segname)) == 0) {
                return sgp;
            }
        }
        sgp = (const segment_command_64 *)((char *)sgp + sgp->cmdsize);
    }
    return NULL;
}

讀取數(shù)據(jù)段中的 data section 數(shù)據(jù)的模擬實(shí)現(xiàn)代碼如下,該函數(shù)定義在<mach-o/getsect.h>頭文件中,但是沒有公布實(shí)現(xiàn)代碼,以下是根據(jù)目標(biāo)文件內(nèi)存布局模擬的實(shí)現(xiàn)邏輯。原理是:

  • 首先通過getsegbynamefromheader(...)獲取目標(biāo)數(shù)據(jù)段sgp
  • 然后計(jì)算數(shù)據(jù)區(qū)起始地址datastart = mhp + sizeofheader + sizeofcmds,表示數(shù)據(jù)區(qū)緊接在 頭信息 和 加載命令區(qū) 后面;
  • 然后遍歷sgp中所有 data section 的元數(shù)據(jù)sectptr。當(dāng)sectname匹配時(shí)返回datastart + sectptr->offset,表示 data section 的數(shù)據(jù)區(qū)域的起始地址;反之則通過sectptr += sizeof(struct section_64)以遍歷到下一個(gè) data section 的元數(shù)據(jù);
extern uint8_t *getsectiondata(
    const struct mach_header_64 *mhp,
    const char *segname,
    const char *sectname,
    unsigned long *size){

    // 1. 獲取目標(biāo)數(shù)據(jù)段
    const segment_command_64 *sgp = getsegbynamefromheader(mhp, segname);
    if (!sgp)
        return NULL;

    uint_32t sizeofcmds = mhp->sizeofcmds;
    uint_32t sizeofheader = sizeof(struct mach_header_64);
    uint8_t * datastart = mhp + sizeofheader + sizeofcmds; // 計(jì)算數(shù)據(jù)區(qū)起始地址

    // 2. 獲取目標(biāo)數(shù)據(jù)段首個(gè) data section 元數(shù)據(jù)(section_64結(jié)構(gòu)體)的起始地址
    uint8_t * sectptr = sgp + sizeof(struct segment_command_64);

    // 3. 遍歷目標(biāo)數(shù)據(jù)段的所有 data section
    for(int i = 0; i < sgp->nsects; i++) {
        if (strncmp(sectptr->sectname, sectname, sizeof(sectptr->sectname)) == 0) {
            if(size > sectptr->size)
                return NULL;  // 獲取字節(jié)數(shù)超出 section 數(shù)據(jù)范圍應(yīng)該給出錯(cuò)誤提示

            uint8_t *sectdataptr = datastart + sectptr->offset;
            return sectdataptr;
        }
            
        sectptr += sizeof(struct section_64); // 偏移到下一個(gè)
    }
    return NULL;
}

三、鏡像內(nèi)容映射到內(nèi)存 map_images

載入鏡像后進(jìn)入特定的狀態(tài)將觸發(fā)_objc_init(...)初始化 Objective-C 運(yùn)行環(huán)境時(shí)注冊(cè)的三個(gè)回調(diào)。完成鏡像綁定后將觸發(fā)map_images回調(diào),加載鏡像中定義的 Objective-C 元素。傳入回調(diào)的參數(shù)包括:

  • 待處理鏡像頭信息的數(shù)組const struct mach_header * const mhdrs[];
  • 鏡像的路徑const char * const paths[]
  • 鏡像頭信息數(shù)組的長(zhǎng)度unsigned count。

map_images(...)函數(shù)直接調(diào)用了map_images_nolock (...)函數(shù),該函數(shù)包含很多底層知識(shí)點(diǎn),非常晦澀,這里并沒有深入分析,總結(jié)其功能為:

  • 預(yù)處理優(yōu)化初始相關(guān),該部分邏輯公開的細(xì)節(jié)很少可以忽略;
  • 通過addHeader(...)匯總鏡像中的 Objective-C 元素,生成head_info的數(shù)組,作為下一步的輸入;
  • 調(diào)用_read_images(...)加載鏡像中的 Objective-C 元素,如類、分類、協(xié)議、消息等等。

第3點(diǎn)是其核心功能。以下是map_images(...)map_images_nolock(...)的源代碼,代碼中標(biāo)注了“核心邏輯”的部分將在后文分別詳細(xì)介紹。

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

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // 預(yù)優(yōu)化環(huán)境初始化
    if (firstTime) {
        preopt_init();
    }

    hCount = 0;  // 所有 包含Objective-C元素的鏡像
    int totalClasses = 0;  // 類的總數(shù)
    int unoptimizedTotalClasses = 0;  // 未優(yōu)化的類的總數(shù)
    {
        // 遍歷鏡像中所有元素信息,逐一轉(zhuǎn)換成header_info,并添加到hList
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[I];

            // (核心邏輯1)addHeader內(nèi)部邏輯將headerType轉(zhuǎn)化為header_info類型并添加到一張全局的
            // 鏈表中,返回header_info類型的轉(zhuǎn)化結(jié)果
            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) continue;
            
            if (mhdr->filetype == MH_EXECUTE) {
                // 一些可執(zhí)行文件的初始化代碼
#if __OBJC2__
                size_t count;
                _getObjc2SelectorRefs(hi, &count);
                selrefCount += count;
                _getObjc2MessageRefs(hi, &count);
                selrefCount += count;
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
                
                // 忽略GC兼容檢查代碼
                ...

            }
            
            hList[hCount++] = hi;
        }
    }

    if (firstTime) {
        //初始化一些最基本的選擇器,如alloc、dealloc、initialize、load等等
        sel_init(selrefCount);

        // 初始化AutoreleasePool和SideTable
        arr_init();

        // 忽略GC兼容檢查代碼
        ...

        // 忽略針對(duì)MAC OS X平臺(tái)的initialize的fork安全檢查代碼(Fixme: 不懂)
        ...

    }

    if (hCount > 0) {
        // (核心邏輯2)加載二進(jìn)制文件中的元素,包括類、分類、協(xié)議等等
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
}

void sel_init(size_t selrefCount)
{
    // 保存到SelrefCount靜態(tài)的int中,只是為了后面初始化namedSelectors哈希表時(shí)提供初始size
    SelrefCount = selrefCount;

#if SUPPORT_PREOPT
    // 預(yù)處理優(yōu)化Selector,忽略
    builtins = preoptimizedSelectors();

    if (PrintPreopt  &&  builtins) {
        uint32_t occupied = builtins->occupied;
        uint32_t capacity = builtins->capacity;
    }
#endif

// sel_registerNameNoLock用于將SEL添加到全局的namedSelectors哈希表中,方便系統(tǒng)
// 快速判斷某個(gè)SEL是否可識(shí)別
#define s(x) SEL_##x = sel_registerNameNoLock(#x, NO)
#define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO)

    mutex_locker_t lock(selLock);

    s(load);
    s(initialize);
    t(resolveInstanceMethod:, resolveInstanceMethod);
    t(resolveClassMethod:, resolveClassMethod);
    t(.cxx_construct, cxx_construct);
    t(.cxx_destruct, cxx_destruct);
    s(retain);
    s(release);
    s(autorelease);
    s(retainCount);
    s(alloc);
    t(allocWithZone:, allocWithZone);
    s(dealloc);
    s(copy);
    s(new);
    t(forwardInvocation:, forwardInvocation);
    t(_tryRetain, tryRetain);
    t(_isDeallocating, isDeallocating);
    s(retainWeakReference);
    s(allowsWeakReference);

#undef s
#undef t
}

void arr_init(void) 
{
    AutoreleasePoolPage::init();  //自動(dòng)釋放池初始化,后續(xù)獨(dú)立文章介紹
    SideTableInit();  //SideTable初始化,后續(xù)獨(dú)立文章介紹
}

3.1 核心邏輯 addHeader

addHeader的作用不僅僅在于返回headerType對(duì)應(yīng)的header_info結(jié)構(gòu)體,其內(nèi)部調(diào)用了appendHeader()將所有已加載的鏡像的元素所對(duì)應(yīng)的header_info串聯(lián)成鏈表結(jié)構(gòu),鏈表首節(jié)點(diǎn)為FirstHeader、末尾節(jié)點(diǎn)為LastHeader。該鏈表在后面卸載鏡像元素時(shí)需要用到。至此完成鏡像數(shù)據(jù)收集工作。

3.1.1 addHeader 所收集數(shù)據(jù) header_info

addHeader所收集的信息header_info結(jié)構(gòu)體的功能如下:

  • 用于快速定位鏡像中的頭信息(mach_header_64結(jié)構(gòu)體)、鏡像信息。鏡像信息用于標(biāo)記鏡像的屬性,以objc_image_info結(jié)構(gòu)體格式保存于 鏡像文件的__DATA數(shù)據(jù)段的__objc_imageinfo data section,鏡像信息在構(gòu)建鏡像文件時(shí)生成;
  • 包含header_info_rw rw_data[]用于記錄鏡像以及鏡像中的 Objective-C 元素的狀態(tài),該成員的數(shù)據(jù)是可讀寫的。

鏡像具體包含的狀態(tài)見header_info_rw結(jié)構(gòu)體:

  • 最低位isLoaded標(biāo)記鏡像是否已加載;
  • 次低位allClassesRealized標(biāo)記鏡像中所有的 Objective-C 類是否已完成 class realizing;
  • 其他位用于保存下一個(gè)header_info節(jié)點(diǎn)的地址,由于地址的最低 3 位必為0因此實(shí)際上僅需保存最低 3 位之外的位即可,又因?yàn)?code>header_info_rw僅用了最低 2 位作為特殊標(biāo)記位,因此指定當(dāng)前header_info* curHeader指向下一個(gè)節(jié)點(diǎn)header_info* nextHeader時(shí),僅需設(shè)置((header_info_rw*)&curHeader->rw_data[0])->next = nextHeader >> 2。
typedef struct header_info {
private:
    // 保存鏡像中的頭信息mach_header_64相對(duì)當(dāng)前header_info的內(nèi)存地址偏移
    intptr_t mhdr_offset;

    // 保存鏡像中的鏡像信息相對(duì)當(dāng)前header_info
    // 的內(nèi)存地址偏移
    intptr_t info_offset;

public:

    // 公開的API
    ...

private:
    // 雖然定義為數(shù)組,但是包含的元素基本為1個(gè),不會(huì)超過1個(gè)。
    header_info_rw rw_data[];
} header_info;

typedef struct header_info_rw {

    // 公開的API
    ...

private:
#ifdef __LP64__
    uintptr_t isLoaded              : 1;
    uintptr_t allClassesRealized    : 1;
    uintptr_t next                  : 62;
#else
    uintptr_t isLoaded              : 1;
    uintptr_t allClassesRealized    : 1;
    uintptr_t next                  : 30;
#endif
} header_info_rw;

typedef struct objc_image_info {
    uint32_t version; // currently 0
    uint32_t flags;

#if __cplusplus >= 201103L
  private:
    enum : uint32_t {
        IsReplacement       = 1<<0,  // used for Fix&Continue, now ignored
        SupportsGC          = 1<<1,  // image supports GC
        RequiresGC          = 1<<2,  // image requires GC
        OptimizedByDyld     = 1<<3,  // image is from an optimized shared cache
        CorrectedSynthesize = 1<<4,  // used for an old workaround, now ignored
        IsSimulated         = 1<<5,  // image compiled for a simulator platform
        HasCategoryClassProperties  = 1<<6,  // class properties in category_t
        // not yet used = 1<<7

        // Swift版本兼容相關(guān),忽略
        ...

    };
  public:
    
    // Swift版本兼容相關(guān),忽略
    ...

  public:
    // 公開的API,忽略
    ...

#endif
} objc_image_info;

3.1.2 addHeader 具體處理流程

具體處理流程及細(xì)節(jié)注釋在下列源代碼中。

#define SEG_OBJC    "__OBJC"

header_info *FirstHeader = 0; 
header_info *LastHeader  = 0; 
int HeaderCount = 0;

static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)
{
    header_info *hi;
    if (bad_magic(mhdr)) return NULL;  // 校驗(yàn)magic,忽略

    bool inSharedCache = false;
    hi = preoptimizedHinfoForHeader(mhdr);  // 預(yù)處理優(yōu)化相關(guān),忽略
    if (hi) {
        // 預(yù)處理優(yōu)化相關(guān),忽略
        // 在 dyld shared cache 中查找到mhdr
        // 不允許重復(fù)加載
        if (hi->isLoaded()) return NULL;
        inSharedCache = true;
        hi->setLoaded(true);
    }
    else 
    {
        // 在 dyld shared cache 中未查找到mhdr

        // 1. 不允許重復(fù)添加header_info到鏈表。getNext()用于獲取下一個(gè)header_info節(jié)點(diǎn)
        for (hi = FirstHeader; hi; hi = hi->getNext()) {
            if (mhdr == hi->mhdr()) return NULL;
        }

        size_t info_size = 0;
        unsigned long seg_size;
        const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size); // 獲取鏡像信息
        const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size); // 獲取__OBJC數(shù)據(jù)段
        
        // 2. 若__OBJC數(shù)據(jù)段、鏡像信息為空,則直接返回NULL
        if (!objc_segment  &&  !image_info) return NULL;

        // 3. 分配內(nèi)存header_info結(jié)構(gòu)體需要占用的內(nèi)存空間,為header_info結(jié)構(gòu)體占用字節(jié)數(shù)、header_info_rw
        // 結(jié)構(gòu)體的占用字節(jié)數(shù)之和,因?yàn)閔eader_info的rw_data數(shù)組僅包含1個(gè)header_info_rw元素
        hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);

        // 4. 設(shè)置 header_info 中用于定位鏡像頭信息的 mhdr_offset
        hi->setmhdr(mhdr);

        // 5. 設(shè)置 header_info 中用于定位鏡像信息的 info_offset
        static const objc_image_info emptyInfo = {0, 0};
        hi->setinfo(image_info ?: &emptyInfo); 

        // 6. 設(shè)置鏡像isLoaded為YES,表示鏡像已加載
        hi->setLoaded(true);
        // 7. 設(shè)置鏡像allClassesRealized為NO,表示鏡像中定義的類尚未開始class realizing
        hi->setAllClassesRealized(NO);
    }

    {
        // 8. 統(tǒng)計(jì)鏡像中包含的類的總數(shù)
        size_t count = 0;
        if (_getObjc2ClassList(hi, &count)) {
            totalClasses += (int)count;
            if (!inSharedCache) unoptimizedTotalClasses += count;
        }
    }

    // 9. 將構(gòu)建的header_info添加到全局的已加載鏡像鏈表,添加到鏈表末尾
    appendHeader(hi);
    
    return hi;
}

void appendHeader(header_info *hi)
{
    HeaderCount++;
    hi->setNext(NULL);
    if (!FirstHeader) {
        // 鏈表為空
        FirstHeader = LastHeader = hi;
    } else {
        if (!LastHeader) {
            // 找到最后一個(gè)節(jié)點(diǎn)
            LastHeader = FirstHeader;
            while (LastHeader->getNext()) LastHeader = LastHeader->getNext();
        }
        // 添加到最后一個(gè)節(jié)點(diǎn)后面
        LastHeader->setNext(hi);
        LastHeader = hi;
    }
}

objc_image_info *
_getObjcImageInfo(const headerType *mhdr, size_t *outBytes)
{
    return getDataSection<objc_image_info>(mhdr, "__objc_imageinfo", 
                                           outBytes, nil);
}

3.2 核心邏輯 _read_images

_read_images根據(jù)前面生成的header_info結(jié)構(gòu)體的數(shù)組,加載鏡像中定義的 Objective-C 元素,如類、分類、協(xié)議。下面的代碼非常長(zhǎng),將其分為幾個(gè)部分詳細(xì)分析。從代碼注釋中可以清晰地知道其處理流程。

// 加載鏡像中的 Objective-C 元素
void _read_images(header_info **hList, uint32_t hCount)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t I;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked();

#define EACH_HEADER \
    hIndex = 0;         \
    crashlog_header_name(nil) && hIndex < hCount && (hi = hList[hIndex]) && crashlog_header_name(hi); \
    hIndex++

    // 1. 首次運(yùn)行的初始化配置,注意該邏輯塊內(nèi)的代碼全局只執(zhí)行一次
    if (!doneOnce) {
        doneOnce = YES;

        // 1.1 配置 isa 類型支持相關(guān),忽略
        ...

        // 1.2 配置 tagged pointer 支持相關(guān),忽略
        ...

        // 1.3 初始化gdb_objc_realized_classes
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotal : total) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        
        // 1.4 初始化allocatedClasses哈希表用于保存已完成內(nèi)存分配的類及元類,完成內(nèi)存
        // 分配是指完成類的class_rw_t內(nèi)存分配
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
    }

    // 2. 發(fā)現(xiàn)類,代碼見<3.2.1>
    ...

    // 3. 類重映射,代碼見<3.2.2>
    ...

    // 4. 獲取__objc_selrefs數(shù)據(jù)section中定義的選擇器,添加到namedSelectors哈希表,忽略
    ...

#if SUPPORT_FIXUP
    // 5. 獲取__objc_msgrefs數(shù)據(jù)section中定義的消息,添加消息的選擇器到namedSelectors
    // 哈希表,若其IMP指向預(yù)定義的IMP,則需要保證其指向新版本runtime定義的IMP,忽略
    ...

#endif

    // 6. 發(fā)現(xiàn)協(xié)議,忽略
    ...

    // 7. 協(xié)議排序,忽略
    ...

    // 8. 認(rèn)識(shí)非懶加載類,代碼見<3.2.3>
    ...

    // 9. 認(rèn)識(shí)懶加載類,代碼見<3.2.4>
    ...

    // 10. 發(fā)現(xiàn)分類,代碼見<3.2.5>
    ...

#undef EACH_HEADER
}

3.2.1 發(fā)現(xiàn)類

_read_images(...)中以下代碼用于發(fā)現(xiàn)類。實(shí)際上是構(gòu)建 future class 的實(shí)體類(remapped future class/resolved future class)的過程,可以簡(jiǎn)稱為 future class 解析(future class resolving)。具體步驟是:

  • 首先,通過_getObjc2ClassList(...)讀取header_info指向的鏡像中的__objc_classlist數(shù)據(jù) section,以提取鏡像中定義的所有類;
  • 然后,調(diào)用readClass(...)讀取鏡像中對(duì)應(yīng) future class(保存在全局的future_named_class_map哈希表中)的類的元數(shù)據(jù),構(gòu)建 future class 的實(shí)體類(remapped future class),并將實(shí)體類添加到remappedClasses全局哈希表中;
  • 最后,若readClass(...)返回的類不等于傳入的類,則說明該類已被重映射,將返回的類保存到已解析 future class 局部變量resolvedFutureClasses中。
    for (EACH_HEADER) {
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        
        if (! mustReadClasses(hi)) {
            // 若鏡像有經(jīng)過優(yōu)化,則不需要調(diào)用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);

            // 若readClass返回的類非空且不等于直接從header_info讀出來的類則該類為future class
            if (newCls != cls  &&  newCls) {
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }

3.2.2 類重新映射

_read_images(...)中以下代碼用于類重映射。若發(fā)現(xiàn)類階段構(gòu)建了 future class 的實(shí)體類,則鏡像中 原指向 future class 的類引用 以及父類引用 需要指向?qū)嶓w類,這就是類重映射過程。具體過程如下:

  • 調(diào)用_getObjc2ClassRefs(...)函數(shù)獲取鏡像中__objc_classrefs數(shù)據(jù) section 中保存的所有類引用;
  • 遍歷所有類引用:調(diào)用remapClassRef(Class *clsref)重映射類引用。內(nèi)部邏輯是當(dāng)檢測(cè)到remppedClass哈希表內(nèi)Key為*clsref的Value值newCls不等于*clsref時(shí),表示該類需要重新映射,將clsref指向newCls
  • 調(diào)用_getObjc2SuperRefs(...)函數(shù)獲取鏡像中__objc_superrefs數(shù)據(jù) section 中保存的所有父類引用;
  • 遍歷所有類引用:調(diào)用remapClassRef(Class *clsref)重映射父類引用。
    // 若發(fā)現(xiàn)類階段有處理 future class
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                // 重新映射鏡像中定義的類的引用
                remapClassRef(&classrefs[I]);
            }
            
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                // 重新映射鏡像中包含的 super
                remapClassRef(&classrefs[I]);
            }
        }
    }

3.2.3 認(rèn)識(shí)非懶加載類

完成 future class 解析和類重映射后,需要加載的類的內(nèi)存地址即可確定。此時(shí)需要對(duì)需要加載的類進(jìn)行 class realizing 以確定其class_rw_t數(shù)據(jù)的內(nèi)存地址,以及確定類的對(duì)象內(nèi)存布局。非懶加載類的 class realizing 的代碼如下,通過調(diào)用realizeClassWithoutSwift(...)函數(shù)實(shí)現(xiàn):

    for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            // 模擬器環(huán)境特殊配置,忽略
            ...
            
            addClassTableEntry(cls);

            // Swift環(huán)境兼容相關(guān),忽略
            ...

            realizeClassWithoutSwift(cls);
        }
    }

3.2.4 認(rèn)識(shí) resolved future class

繼續(xù)進(jìn)行已解析的 future class 的 class realizing 過程,代碼如下:

    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            realizeClass(resolvedFutureClasses[I]);
            resolvedFutureClasses[i]->setRequiresRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }    

3.2.5 分類發(fā)現(xiàn)

分類中擴(kuò)展類的遵循協(xié)議列表、屬性列表、方法列表需要在運(yùn)行時(shí)添加到類的class_rw_t數(shù)據(jù)中,該過程必須在類的內(nèi)存地址、class_rw_t內(nèi)存地址、類的對(duì)象內(nèi)存布局確定后方能進(jìn)行。大致過程如下:

  • 調(diào)用_getObjc2CategoryList(...)函數(shù)獲取鏡像的__objc_catlist數(shù)據(jù) section 中保存的鏡像中定義的分類列表;
  • 遍歷分類列表;
  • 若分類所擴(kuò)展的類為空則報(bào)錯(cuò),并直接處理下一個(gè)分類;
  • 若分類擴(kuò)展元素包括協(xié)議列表或?qū)嵗龑傩曰驅(qū)嵗椒?,則將調(diào)用addUnattachedCategoryForClass(...)將分類添加到擴(kuò)展類的待處理分類哈希表,然后調(diào)用remethodizeClass(...)將協(xié)議列表或?qū)嵗龑傩曰驅(qū)嵗椒ㄔ靥砑拥綌U(kuò)展類的class_rw_t數(shù)據(jù)中;
  • 若分類擴(kuò)展元素包括協(xié)議列表或類屬性或類方法,則將調(diào)用addUnattachedCategoryForClass(...)將分類添加到擴(kuò)展類的元類的待處理分類哈希表,然后調(diào)用remethodizeClass(...)將協(xié)議列表或?qū)嵗龑傩曰驅(qū)嵗椒ㄔ靥砑拥綌U(kuò)展類的元類的class_rw_t數(shù)據(jù)中;
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);

            if (!cls) {
                // 分類所擴(kuò)展的類為空則報(bào)錯(cuò)
                catlist[i] = nil;
                continue;
            }

            // 協(xié)議、實(shí)例方法、實(shí)例屬性添加到類的class_rw_t中
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }

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

四、初始化類庫(kù)load_images

第三章介紹的map_images用于匯總鏡像信息,并加載其中定義的 Objective-C 元素,load_images的邏輯基于map_images所加載的類、分類信息進(jìn)行。load_images主要調(diào)用了兩個(gè)函數(shù):

  • prepare_load_methods(...):執(zhí)行類、分類的load方法進(jìn)行類的初始化之前的準(zhǔn)備工作,主要是收集鏡像中實(shí)現(xiàn)了load方法的類和分類;
  • void call_load_methods(void):調(diào)用類、分類中的load方法初始化類;

prepare_load_methodscall_load_methods是成對(duì)存在的兩個(gè)操作,前者記錄實(shí)現(xiàn)了load方法的類、分類,后者按正確的順序調(diào)用所記錄的類和分類的load方法。

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // 若鏡像中的所有類、分類均未實(shí)現(xiàn)load方法,則直接返回
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // 收集鏡像中實(shí)現(xiàn)了load方法的類和分類
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // 執(zhí)行prepare_load_methods收集的類和分類的load方法
    call_load_methods();
}

4.1 prepare_load_methods函數(shù)

分析prepare_load_methods函數(shù)邏輯前,首先看兩個(gè)數(shù)據(jù)結(jié)構(gòu)loadable_class、loadable_category分別表示包含load方法的類和分類。聲明兩個(gè)靜態(tài)數(shù)組容器loadable_class的數(shù)組loadable_classes,以及loadable_category的數(shù)組loadable_categories,分別用于記錄包含了load方法但尚未調(diào)用load的類和分類。prepare_load_methods的主邏輯就是操作上述兩個(gè)數(shù)組容器。

loadable_classes數(shù)組支持動(dòng)態(tài)擴(kuò)容,占用內(nèi)存存在冗余空間,因此不僅需要loadable_classes_used記錄loadable_classes實(shí)際保存的loadable_class結(jié)構(gòu)體數(shù)量,還需要loadable_classes_allocated記錄為數(shù)組分配的用于保存loadable_class結(jié)構(gòu)體的內(nèi)存單元數(shù)量。loadable_categories同理。

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

struct loadable_category {
    Category cat;  // may be nil
    IMP method;
};

// 數(shù)組容器,記錄包含load方法的所有類的信息
static struct loadable_class *loadable_classes = nil;
//數(shù)組內(nèi)存中存在冗余空間,用loadable_classes_used實(shí)際保存的單元數(shù)量
static int loadable_classes_used = 0;  
//數(shù)組內(nèi)存中存在冗余空間,因此用loadable_classes_allocated記錄分配的單元數(shù)量
static int loadable_classes_allocated = 0; 

// 數(shù)組容器,記錄包含load方法的所有分類的信息
static struct loadable_category *loadable_categories = nil;
static int loadable_categories_used = 0;
static int loadable_categories_allocated = 0;

prepare_load_methods包含兩大步驟:

  • 獲取鏡像中所有非懶加載類,依次調(diào)用schedule_class_load(...)函數(shù)將包含load方法的類添加到loadable_class數(shù)組容器。schedule_class_load(...)函數(shù)內(nèi)部,在調(diào)用add_class_to_loadable_list(cls)將類loadable_class數(shù)組容器前,遞歸調(diào)用了schedule_class_load(cls->superclass),以保證父類的load方法先于子類load方法執(zhí)行;
  • 獲取鏡像中所有非懶加載分類,依次調(diào)用add_category_to_loadable_list(...)函數(shù)將包含load方法的類添加到loadable_category數(shù)組容器。注意在這之前需要保證分類所擴(kuò)展的類完成 class realizing;
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, I;

    runtimeLock.assertWriting();

    // 獲取二進(jìn)制文件中,非懶加載的所有類的信息
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    // 獲取二進(jìn)制文件中,非懶加載的所有分類的信息
    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;  
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

// ------- 將類添加到loadable_classes數(shù)組 ------- //
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized()); 

    if (cls->data()->flags & RW_LOADED) return;

    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  

    // loadable_classes數(shù)組擴(kuò)容,因此loadable_classes數(shù)組中是存在冗余空間的,這是loadable_classes_allocated存在原因
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

// ------- 將分類類添加到loadable_categories數(shù)組 ------- //
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);
    if (!method) return;
    
    // loadable_categories數(shù)組擴(kuò)容
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

4.2 call_load_methods函數(shù)

分析完prepare_load_methods的邏輯,其實(shí)call_load_methods的邏輯已經(jīng)比較明顯了。就是從loadable_classes容器及loadable_categories容器中推出類和分類,依次調(diào)用load方法。

call_load_methods(void)代碼的邏輯比較怪異,在do-while循環(huán)內(nèi)部的while循環(huán)明明已經(jīng)判斷loadable_classes_used <= 0,為什么在do-while還要判斷loadable_classes_used > 0進(jìn)入下一次迭代?這是因?yàn)轭?、分類?code>load方法中,均可能存在動(dòng)態(tài)加載鏡像文件的邏輯,從而引入新的類、分類的load方法。do-while循環(huán)內(nèi)部,執(zhí)行類的load方法使用了一個(gè)while循環(huán),而執(zhí)行分類的load方法則只調(diào)用了一次,這是因?yàn)榉诸?code>load方法必須等待其擴(kuò)展類的load方法執(zhí)行完畢才能執(zhí)行,因此需要立即進(jìn)入下一次迭代以執(zhí)行擴(kuò)展類的load方法。

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. 遍歷并執(zhí)行類的所有可調(diào)用的load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. 執(zhí)行分類的load方法
        more_categories = call_category_loads();

        // 3. 循環(huán)直到類及分類的所有l(wèi)oad方法均被執(zhí)行
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

4.2.1 執(zhí)行類的load方法

由于在prepare_load_methods已經(jīng)確定了類的load方法執(zhí)行順序,因此call_class_loads(void)僅需簡(jiǎn)單迭代執(zhí)行loadable_class中的load方法即可。處理過程大致如下:

  • 將局部變量classes指向loadable_classes,將loadable_classes指向nil。classes表示本次需要執(zhí)行的所有類的load方法,為舊容器。loadable_classes表示本次執(zhí)行的類的load方法中動(dòng)態(tài)載入的所有新類的load方法,為新容器;
  • 遍歷classes中所有loadable_class結(jié)構(gòu)體,執(zhí)行其method所指向的load方法。遍歷classes時(shí),若load方法中載入了新的類的load方法,則又會(huì)被收集于loadable_classes所指向的新容器中;
  • 釋放classes局部變量所指向的舊容器內(nèi)存空間;
static void call_class_loads(void)
{
    int I;
    
    struct loadable_class *classes = loadable_classes;
    // loadable_classes指向nil
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
   
    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; 

        // 執(zhí)行l(wèi)oad方法。load方法可能包含動(dòng)態(tài)加載鏡像的邏輯,此時(shí)loadable_classes則會(huì)指向
        // 新的容器來收集動(dòng)態(tài)加載鏡像中的load方法
        (*load_method)(cls, SEL_load); 
    }
    
    if (classes) free(classes);  // 釋放舊容器
}

4.2.2 執(zhí)行分類的load方法

執(zhí)行分類的load方法的邏輯比類稍微復(fù)雜。處理過程如下:

  • 局部變量cats指向loadable_categories表示舊容器。loadable_categories指向nil表示新容器;
  • 遍歷舊容器中的所有loadable_category結(jié)構(gòu)體,若loadable_categorycls成員非空且可加載,則執(zhí)行method成員指向的load方法,并把cat成員置nil;
  • cats收集 舊容器中未執(zhí)行load方法的所有分類(判斷cat成員非空);
  • cats收集 執(zhí)行舊容器的load方法過程中動(dòng)態(tài)載入的所有分類;
  • cats保存的loadable_category結(jié)構(gòu)體數(shù)量大于0,則設(shè)置loadable_categories指向cats所指向的內(nèi)存空間;反之loadable_categoriesnil。
static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    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;

    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()) {  //cls->isLoadable()恒為YES
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }

    // 收集上面for循環(huán)未執(zhí)行l(wèi)oad方法的所有分類,其中包含了load方法中可能存在動(dòng)態(tài)加載
    // 鏡像時(shí)載入的分類的load方法,這些load方法不能立刻執(zhí)行,需要其擴(kuò)展類的load方法
    // 執(zhí)行完畢后才能執(zhí)行。
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[I];
        } else {
            shift++;
        }
    }
    // loadable_categories舊容器中尚未執(zhí)行l(wèi)oad方法的loadable_category結(jié)構(gòu)體數(shù)量,這些
    // loadable_category均保留在loadable_categories新容器
    used -= shift;  

    // 若loadable_categories_used大于0,說明在執(zhí)行分類load方法時(shí)收集到新的分類load方法
    new_categories_added = (loadable_categories_used > 0);

    // 將新收集的分類load方法添加到loadable_categories新容器
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[I];
    }

    // 釋放舊loadable_categories容器
    if (loadable_categories) free(loadable_categories);

    // 賦值新loadable_categories容器
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }

    return new_categories_added;
}

五、總結(jié)

  • 應(yīng)用加載的本質(zhì)是加載應(yīng)用沙盒中的可執(zhí)行文件(也是目標(biāo)文件、鏡像文件),鏡像文件可以映射到內(nèi)存空間生成鏡像。鏡像中包含了頭信息、加載命令、數(shù)據(jù)區(qū),鏡像中定義的 Objective-C 元素的元信息主要保存在__DATA數(shù)據(jù)段、__CONST_DATA數(shù)據(jù)段中。例如__objc_classlist保存鏡像中定義的所有類、__objc_catlist保存鏡像中定義的所有分類;

  • 生成鏡像后,觸發(fā)map_images回調(diào)讀取鏡像中定義的 Objective-C 元素的元信息,主要包括發(fā)現(xiàn)類、發(fā)現(xiàn)協(xié)議、認(rèn)識(shí)類、將分類中的方法添加到類、元類的class_rw_t的方法列表中;

  • 完成鏡像綁定后,觸發(fā)load_images收集需要調(diào)用的類及分類的load方法,其優(yōu)先級(jí)是靜態(tài)加載的父類>靜態(tài)加載的子類>靜態(tài)加載的分類>動(dòng)態(tài)加載的父類>動(dòng)態(tài)加載的子類>動(dòng)態(tài)加載的分類;

  • 下一篇介紹 runtime 實(shí)現(xiàn)基于引用計(jì)數(shù)的內(nèi)存管理機(jī)制。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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