iOS 底層探索:類的加載上(dyld和objc關(guān)聯(lián))

文集:iOS 底層探索之路

前言

  • 在啟動(dòng) app 的時(shí)候债朵, dyld 會(huì)對(duì)動(dòng)態(tài)庫(kù)進(jìn)行加載琳猫、鏈接等一系列動(dòng)作咕娄,之后就會(huì)來(lái)到 libobjc.A.dylib 庫(kù)中調(diào)用 _objc_init 對(duì)類進(jìn)行處理,通過(guò) map_images 映射出整個(gè)鏡像文件女气,再通過(guò) read_images 加載鏡像文件,此時(shí)類已經(jīng)加載完成掉缺,那其中類的加載的流程又是怎么樣的呢?之前的篇章探索了OC底層的內(nèi)容局骤,類的屬性攀圈、方法暴凑、協(xié)議都是怎么加載的呢峦甩?接下來(lái)開(kāi)始類的加載dyld和objc關(guān)聯(lián)的分析。

準(zhǔn)備工作

  • 了解: 動(dòng)態(tài)鏈接器dyld的加載流程 :
      1. APP是由內(nèi)核引導(dǎo)啟動(dòng)的现喳,kernel內(nèi)核做好所有準(zhǔn)備工作后會(huì)得到線程入口及main入口凯傲,但是線程不會(huì)馬上進(jìn)入main入口,因?yàn)檫€要加載動(dòng)態(tài)鏈接器(dyld)嗦篱,dyld會(huì)將入口點(diǎn)保存下來(lái)冰单,等dyld加載完所有動(dòng)態(tài)鏈接庫(kù)等工作之后,再開(kāi)始執(zhí)行main函數(shù)灸促。
      1. 系統(tǒng)kernel做好啟動(dòng)程序的初始準(zhǔn)備后诫欠,交給dyld負(fù)責(zé)。dyld接手后浴栽,系統(tǒng)先讀取 App 的可執(zhí)行文件(Mach-O文件)荒叼,從里面獲取dyld的路徑,然后加載dyld典鸡,dyld去初始化運(yùn)行環(huán)境被廓,開(kāi)啟緩存策略,配合 ImageLoader 將二進(jìn)制文件按格式加載到內(nèi)存萝玷,加載程序相關(guān)依賴庫(kù)(其中也包含我們的可執(zhí)行文件)嫁乘,并對(duì)這些庫(kù)進(jìn)行鏈接昆婿,最后調(diào)用每個(gè)依賴庫(kù)的初始化方法,在這一步蜓斧,runtime被初始化仓蛆。當(dāng)所有依賴庫(kù)初始化后,輪到最后一位(程序可執(zhí)行文件)進(jìn)行初始化法精,在這時(shí)runtime會(huì)對(duì)項(xiàng)目中所有類進(jìn)行類結(jié)構(gòu)初始化多律,然后調(diào)用所有的load方法。最后dyld返回main()函數(shù)地址搂蜓,main()函數(shù)被調(diào)用狼荞。基本流程如圖:
        dyld的基本加載流程 .png
  • 在main函數(shù)之前有茫茫多的系統(tǒng)操作流程帮碰,這里只是做大概的思路整理相味,但是為了分析類的加載我們首先明確一點(diǎn) main函數(shù)是我們App程序的入口點(diǎn)函數(shù),在App的main函數(shù)之前殉挽,系統(tǒng)會(huì)首先對(duì)App的runtime運(yùn)行環(huán)境丰涉,做了一系列的初始化操作,動(dòng)態(tài)鏈接器dyld調(diào)用runtime入口函數(shù)斯碌,runtime的入口函數(shù)是_objc_init一死,它是在main函數(shù)之前被dyld調(diào)用的。而+load()方法傻唾,則是在main函數(shù)前被_objc_init調(diào)用投慈,所以_objc_init就是我們研究探索類加載的切入點(diǎn)。

一 冠骄、objc_init分析

我們?cè)趇OS App中設(shè)置符號(hào)斷點(diǎn)_objc_init伪煤,則在App啟動(dòng)時(shí)(進(jìn)入main函數(shù)之前),會(huì)進(jìn)入如下調(diào)用堆棧:


objc_init源碼如下:

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
*啟動(dòng)初始化凛辣。注冊(cè)我們的鏡像通知與dyld抱既。
*在庫(kù)初始化時(shí)間之前由libSystem調(diào)用
**********************************************************************/

// runtime + 類的信息
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // 環(huán)境變量的一些操作
    environ_init();
//對(duì)線程池進(jìn)行初始化的
    tls_init();
  // 系統(tǒng)級(jí)別的 c++ 構(gòu)造函數(shù)調(diào)用
    static_init();
      //runtime運(yùn)行時(shí)環(huán)境初始化,里面主要是unattachedCategories扁誓、allocatedClasses -- 分類初始化
    runtime_init();
 //初始化libobjc的異常處理系統(tǒng)
    exception_init();
    //啟動(dòng)回調(diào)機(jī)制防泵,通常這不會(huì)做什么,因?yàn)樗械某跏蓟际嵌栊缘幕雀遥菍?duì)于某些進(jìn)程捷泞,我們會(huì)迫不及待地加載trampolines dylib
  // cache緩存初始化
   cache_init();
 //啟動(dòng)回調(diào)機(jī)制
 _imp_implementationWithBlock_init();
  // 注冊(cè)回調(diào)通知 &:引用類型函數(shù) 外面變了跟著變,load_images值類型只需要加載
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

  1. environ_init
    方法是讀取影響運(yùn)行時(shí)的環(huán)境變量前普,最后內(nèi)部有一段打印環(huán)境變量的代碼裁良。我將它提取出來(lái)景殷,不做判斷谐岁,打印結(jié)果如下。當(dāng)然也可以在終端使用 export OBJC_HELP=1 指令打印環(huán)境變量贱纠;

  2. tls_init
    主要是對(duì)線程池進(jìn)行初始化的;

  3. static_init
    運(yùn)行 C++ 靜態(tài)構(gòu)造函數(shù)响蕴,在 dyld 調(diào)用我們自定義構(gòu)造函數(shù)之前谆焊;

  4. runtime_init:runtime運(yùn)行時(shí)環(huán)境初始化,里面操作是unattachedCategories浦夷、allocatedClasses(表的初始化)

  5. exception_init
    初始化 libobjc 庫(kù)的異常處理系統(tǒng)辖试,注冊(cè)監(jiān)聽(tīng)異常崩潰的回調(diào),當(dāng)發(fā)生崩潰時(shí)劈狐,就會(huì)來(lái)到 _objc_terminate 函數(shù)里面罐孝;

  6. cache_init: cache緩存初始化

  7. _imp_implementationWithBlock_init :?jiǎn)?dòng)回調(diào)機(jī)制,通常這不會(huì)做什么

  8. _dyld_objc_notify_register 肥缔, dyld注冊(cè)回調(diào)通知:僅供objc運(yùn)行時(shí)使用莲兢,注冊(cè)處理程序,以便在映射续膳,取消映射和初始化objc鏡像時(shí)調(diào)用改艇。Dyld 將使用包含objc_image_info的鏡像文件的數(shù)組回調(diào)"mapped"函數(shù)。

接下來(lái)重點(diǎn)分析將dyld和objc關(guān)聯(lián)的_dyld_objc_notify_register

二 坟岔、_dyld_objc_notify_register分析
_dyld_objc_notify_register

首先我們查看源碼:

//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// initializers in that image.  This is when objc calls any +load methods in that image.
//
// 翻譯如下:

//注意:僅供objc運(yùn)行時(shí)使用
//注冊(cè)要映射谒兄,未映射和初始化objc圖像的處理程序。
// Dyld將使用包含objc-image-info節(jié)的圖像數(shù)組回調(diào)“映射”函數(shù)社付。
//這些是dylib的圖像將自動(dòng)增加引用計(jì)數(shù)承疲,因此objc不再需要
//對(duì)它們調(diào)用dlopen()以防止它們被卸載。 在調(diào)用_dyld_objc_notify_register()的過(guò)程中瘦穆,
// dyld將使用已加載的objc圖像調(diào)用“映射”函數(shù)纪隙。 在以后的任何dlopen()調(diào)用期間赊豌,
// dyld也將調(diào)用“映射”函數(shù)扛或。 當(dāng)調(diào)用dyld時(shí),dyld將調(diào)用“ init”函數(shù)
//該圖片中的初始值設(shè)定項(xiàng)碘饼。 這是objc在該圖像中調(diào)用任何+ load方法的時(shí)候熙兔。
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

根據(jù)注釋,我們可以知道艾恼,共注冊(cè)了三個(gè)事件的回調(diào):

  • _dyld_objc_notify_mapped : OC image被加載映射到內(nèi)存(+load()方法在此時(shí)被調(diào)用)
  • _dyld_objc_notify_init : OC image被init時(shí)調(diào)用
  • _dyld_objc_notify_unmapped : OC image被移除內(nèi)存時(shí)調(diào)用

通過(guò)這個(gè)注釋我們會(huì)發(fā)現(xiàn)住涉,整個(gè) objc 在這個(gè)里面是一個(gè)運(yùn)行時(shí)環(huán)境,運(yùn)行時(shí)環(huán)境去加載所有類的一些信息的時(shí)候钠绍,就會(huì)依賴這個(gè)注冊(cè)函數(shù)的回調(diào)的通知舆声,告訴當(dāng)前的 dyld 的做了哪些事情,你需要哪些環(huán)境來(lái)進(jìn)行彼此的通訊,所以這其實(shí)是一個(gè)跨庫(kù)的通知媳握,我們?cè)偃?a target="_blank">dyld3源碼中去查找:

在dyldAPIs.cpp 中

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

typedef void (*_dyld_objc_notify_mapped)(unsigned count, const char* const paths[], const struct mach_header* const mh[]);
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
typedef void (*_dyld_objc_notify_unmapped)(const char* path, const struct mach_header* mh);

跳轉(zhuǎn)到 registerObjCNotifiers

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped; //&map_images
    sNotifyObjCInit     = init; // load_images
    sNotifyObjCUnmapped = unmapped; //unmap_image

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

(1). 當(dāng)image被dyld加載到內(nèi)存后碱屁,會(huì)調(diào)用回調(diào)_dyld_objc_notify_mapped 。在runtime中蛾找,對(duì)應(yīng)的函數(shù)是: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);
}

map_images 方法在 image 加載到內(nèi)存的時(shí)候會(huì)觸發(fā)該方法的調(diào)用娩脾。忽略內(nèi)部函數(shù)跳轉(zhuǎn)、打印和操作 hCount 的代碼打毛,最終會(huì)來(lái)到 _read_images,其核心是用來(lái)讀取Mach-O格式文件的runtime相關(guān)的section信息柿赊,并轉(zhuǎn)化為runtime內(nèi)部的數(shù)據(jù)結(jié)構(gòu)。

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{ 
   ......
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
   ......
}

分析_read_images,由于其源碼太多幻枉,所以只貼上部分代碼:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
// 如果是第一次進(jìn)來(lái)碰声,就會(huì)走 if 下面的代碼
    if (!doneOnce){...}
// 將所有SEL都注冊(cè)到哈希表中,是另外一張哈希表
 static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {...}
   ts.log("IMAGE TIMES: fix up selector references");

    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    // 從編譯后的類列表中取出所有類熬甫,獲取到的是一個(gè)classref_t類型的指針
    for (EACH_HEADER) {...}
ts.log("IMAGE TIMES: discover classes");

    // 主要是修復(fù)重映射 - !noClassesRemapped() 在這里為 false奥邮,所以一般走不進(jìn)來(lái)
    // 將未映射Class和Super Class重映射,被remap的類都是非懶加載的類
    if (!noClassesRemapped())  {...}
   ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    // Fix up old objc_msgSend_fixup call sites
    // 修復(fù)舊的函數(shù)指針調(diào)用遺留
    for (EACH_HEADER){...}
}
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
    bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
    // Discover protocols. Fix up protocol refs.
    // 遍歷所有協(xié)議列表罗珍,并且將協(xié)議列表加載到Protocol的哈希表中
    for (EACH_HEADER){...}
  ts.log("IMAGE TIMES: discover protocols");
    // 修復(fù)協(xié)議列表引用洽腺,優(yōu)化后的 images 可能是正確的,但是并不確定
    for (EACH_HEADER) {...}
  ts.log("IMAGE TIMES: fix up @protocol references");
    if (didInitialAttachCategories) {...}
  ts.log("IMAGE TIMES: discover categories");
    // 實(shí)現(xiàn)非懶加載的類覆旱,對(duì)于load方法和靜態(tài)實(shí)例變量
    for (EACH_HEADER) {...}
  ts.log("IMAGE TIMES: realize non-lazy classes");
    // Realize newly-resolved future classes, in case CF manipulates them
    // 遍歷 resolvedFutureClasses 數(shù)組蘸朋,實(shí)現(xiàn)懶加載的類
    // resolvedFutureClasses 數(shù)組是在第二步的時(shí)候添加懶加載類的
    if (resolvedFutureClasses)  {...}
 ts.log("IMAGE TIMES: realize future classes");

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }

    // Print preoptimization statistics
    if (PrintPreopt){...}
...
}

_read_images大概過(guò)程: 4和9有關(guān)類的處理是我們關(guān)注的重點(diǎn)

·1∶條件控制進(jìn)行一次的加載
·2∶修復(fù)預(yù)編譯階段的@selector的混亂問(wèn)題
·3∶錯(cuò)誤混亂的類處理
·4∶修復(fù)重映射一些沒(méi)有被鏡像文件加載進(jìn)來(lái)的類
·5∶修復(fù)一些消息!
·6∶當(dāng)我們類里面有協(xié)議的時(shí)候∶readProtocol
·7∶ 修復(fù)沒(méi)有被加載的協(xié)議
·8∶分類處理
·9∶類的加載處理
·10∶沒(méi)有被處理的類 優(yōu)化那些被侵犯的類

我們研究的核心是類的加載,所以接下來(lái)逐步分析重點(diǎn)內(nèi)容:
(1.1) _read_images 方法首先創(chuàng)建了兩張表用來(lái)存儲(chǔ)類扣唱。

// 如果是第一次進(jìn)來(lái)藕坯,就會(huì)走 if 下面的代碼
    if (!doneOnce) {
        // 之后就不會(huì)來(lái)了
        // 為什么只來(lái)一次呢,因?yàn)榈谝淮芜M(jìn)來(lái)的時(shí)候噪沙,類炼彪,協(xié)議,sel正歼,分類都沒(méi)有
        // 需要?jiǎng)?chuàng)建容器來(lái)保存這些東西辐马,這里創(chuàng)建的是兩個(gè)表。
        doneOnce = YES;
         
        //... 忽略一些無(wú)關(guān)緊要的代碼
 
        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        // TaggedPointer 的優(yōu)化處理
        initializeTaggedPointerObfuscator();
 
        // 4/3是 NXMapTable 的負(fù)載因子
        int namedClassesSize =
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        // 實(shí)例化存儲(chǔ)類的哈希表局义,并根據(jù)當(dāng)前類的數(shù)量做動(dòng)態(tài)擴(kuò)容
        // 只要你沒(méi)有在共享緩存的類喜爷,不管實(shí)現(xiàn)或者未實(shí)現(xiàn)都會(huì)在這個(gè)里面
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        // 已經(jīng)被分配的類和元類都會(huì)放在這個(gè)表里
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
    }

(1.2)sel_registerNameNoLock:修復(fù)預(yù)編譯階段的@selector的混亂問(wèn)題分析

 // 修復(fù)預(yù)編譯階段的@selector的混亂問(wèn)題
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                // sel_cname 將 SEL 強(qiáng)轉(zhuǎn)為 char 類型
                const char *name = sel_cname(sels[i]);
                // 注冊(cè) SEL 的操作
              //
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
  • _getObjc2SelectorRefs:其中_getObjc2SelectorRefs的源碼如下,表示獲取Mach-O中的靜態(tài)段__objc_selrefs萄唇,后續(xù)通過(guò)_getObjc2開(kāi)頭的Mach-O靜態(tài)段獲取檩帐,都對(duì)應(yīng)不同的section name
  • 其中SEL --> sel并不是簡(jiǎn)單的字符串,是帶地址的字符串 如下所示另萤,sels[i]與sel字符串一致湃密,但是地址不一致,所以需要調(diào)整為一致的。即fix up
//      function name                 content type     section name
GETSECT(_getObjc2SelectorRefs,        SEL,             "__objc_selrefs"); 
GETSECT(_getObjc2MessageRefs,         message_ref_t,   "__objc_msgrefs"); 
GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");
GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
GETSECT(_getObjc2ClassList,           classref_t const,      "__objc_classlist");
GETSECT(_getObjc2NonlazyClassList,    classref_t const,      "__objc_nlclslist");
GETSECT(_getObjc2CategoryList,        category_t * const,    "__objc_catlist");
GETSECT(_getObjc2CategoryList2,       category_t * const,    "__objc_catlist2");
GETSECT(_getObjc2NonlazyCategoryList, category_t * const,    "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList,        protocol_t * const,    "__objc_protolist");
GETSECT(_getObjc2ProtocolRefs,        protocol_t *,    "__objc_protorefs");
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");

(1.3)重點(diǎn):readClass 分析 :跟class有關(guān)了

  // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    // 從編譯后的類列表中取出所有類泛源,獲取到的是一個(gè)classref_t類型的指針
    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            // 鏡像充分優(yōu)化揍障,我們不需要調(diào)用 readClass()
            continue;
        }

        classref_t const *classlist = _getObjc2ClassList(hi, &count);

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

        for (i = 0; i < count; i++) {
            //數(shù)組中會(huì)取出OS_dispatch_queue_concurrent、OS_xpc_object俩由、NSRunloop等系統(tǒng)類毒嫡,例如CF、Fundation幻梯、libdispatch中的類兜畸。以及自己創(chuàng)建的類
            Class cls = (Class)classlist[I];
            // 通過(guò) readClass 函數(shù)獲取處理后的新類,內(nèi)部主要操作 ro 和 rw 結(jié)構(gòu)體
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
            // 初始化所有懶加載的類需要的內(nèi)存空間碘梢,并將所有的未來(lái)需要處理的類添加到一個(gè)數(shù)組中
            // 現(xiàn)在數(shù)據(jù)沒(méi)有加載到的咬摇,連類都沒(méi)有初始化
            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;
            }
        }
    }

    ts.log("IMAGE TIMES: discover classes");

/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be: 
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by 
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();

    const char *LGPersonName = "LGPerson";
    if (strcmp(mangledName, LGPersonName)==0) {
        printf("%s -readClass!- %s \n",__func__,mangledName);
    }
    
    // 如果某個(gè) cls 的 superclass 是 weak-linked 的并且丟失了,則返回YES煞躬。
    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());
        }
        // 添加到重映射表里面肛鹏,映射為 nil
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (Class newCls = popFutureNamedClass(mangledName)) { //這個(gè)流程暫時(shí)不做處理 可能在未來(lái)某個(gè)階段處理
        // This name was previously allocated as a future class.
        // Copy objc_class to future class's struct.
        // Preserve future's rw data block.
        // 對(duì)未來(lái)新的一些類的 ro 和 rw 的特殊處理,一般不會(huì)進(jìn)去
        if (newCls->isAnySwift()) {
            _objc_fatal("Can't complete future class request for '%s' "
                        "because the real class is too big.", 
                        cls->nameForLogging());
        }
        // 將objc_class復(fù)制到future類的結(jié)構(gòu)中
        // 保存future 類的 rw
        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro();
        memcpy(newCls, cls, sizeof(objc_class));
        rw->set_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) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
       
        ASSERT(getClassExceptSomeSwift(mangledName));
    } else {
// 重點(diǎn)----------------------------進(jìn)到這里來(lái)----------------------------
// 重點(diǎn):從mach-o 文件讀取插入到這兩個(gè)表里面恩沛,自此在內(nèi)存中就可以讀到這個(gè)類了
        // 將 cls 加入到 gdb_objc_realized_classes 表里面去
        addNamedClass(cls, mangledName, replacing);
        // 將 cls 插入到 allocatedClasses 表里面去
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    // 供以后參考:共享緩存從不包含mh_bundle
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    // 到此時(shí)在扰,這個(gè)類在整個(gè)表里就有了,返回
    return cls;
}

注:
// 重點(diǎn):從mach-o 文件讀取插入到這兩個(gè)表里面雷客,自此在內(nèi)存中就可以讀到這個(gè)類了
// 將 cls 加入到 gdb_objc_realized_classes 表里面去
addNamedClass(cls, mangledName, replacing);
// 將 cls 插入到 allocatedClasses 表里面去
addClassTableEntry(cls);

(1.4) remapClassRef :主要是修復(fù)重映射

    // 主要是修復(fù)重映射 - !noClassesRemapped() 在這里為 false芒珠,所以一般走不進(jìn)來(lái)
    // 將未映射Class和Super Class重映射,被remap的類都是非懶加載的類
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            // 重映射Class搅裙,注意是從_getObjc2ClassRefs函數(shù)中取出類的引用
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
        }
    }

    ts.log("IMAGE TIMES: remap classes");

(1.5) fixupMessageRef: 修復(fù)舊的函數(shù)指針調(diào)用遺留

   // Fix up old objc_msgSend_fixup call sites
    // 修復(fù)舊的函數(shù)指針調(diào)用遺留
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            // 內(nèi)部將常用的 alloc皱卓、objc_msgSend 等函數(shù)指針進(jìn)行注冊(cè),并 fix 為新的函數(shù)指針
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

(1.6) readProtocol :讀取并初始化Protocol

 bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
    // Discover protocols. Fix up protocol refs.
    // 遍歷所有協(xié)議列表部逮,并且將協(xié)議列表加載到Protocol的哈希表中
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        // cls = Protocol類娜汁,所有協(xié)議和對(duì)象的結(jié)構(gòu)體都類似,isa都對(duì)應(yīng)Protocol類
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        // 獲取protocol哈希表
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->hasPreoptimizedProtocols();

        if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }
        bool isBundle = hi->isBundle();
        // 從編譯器中讀取并初始化Protocol
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    ts.log("IMAGE TIMES: discover protocols");

(1.7) fixupMessageRef :修復(fù)協(xié)議列表引用

   // 修復(fù)協(xié)議列表引用兄朋,優(yōu)化后的 images 可能是正確的掐禁,但是并不確定
    for (EACH_HEADER) {
        // 需要注意到是,下面的函數(shù)是 _getObjc2ProtocolRefs蜈漓,和上面的
        // At launch time, we know preoptimized image refs are pointing at the
        // shared cache definition of a protocol.  We can skip the check on
        // launch, but have to visit @protocol refs for shared cache images
        // loaded later.
        if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
            continue;
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[I]);
        }
    }

    ts.log("IMAGE TIMES: fix up @protocol references");

(1.8)重點(diǎn):realizeClassWithoutSwift :類的初始化

 // 實(shí)現(xiàn)非懶加載的類穆桂,對(duì)于load方法和靜態(tài)實(shí)例變量
    for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            // 實(shí)現(xiàn)所有非懶加載的類(實(shí)例化類對(duì)象的一些信息宫盔,例如rw)
           // alloc + init的前提是類的存在融虽,就是從image鏡像文件中讀取并實(shí)現(xiàn)完畢之后的
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

初始化類就在這一步,首先將非懶加載類從 Mach-O 里面讀取出來(lái)灼芭,然后通過(guò)realizeClassWithoutSwift 實(shí)例化 rw有额。

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    // 判斷 cls 是否已經(jīng)初始化,里面是對(duì) data()->flags 的判斷
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
    // 驗(yàn)證類不在共享緩存的未刪除部分
    auto ro = (const class_ro_t *)cls->data();
    // 判斷類是否是未實(shí)現(xiàn)的未來(lái)類
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // 是未來(lái)的類. rw 已經(jīng)被初始化
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        // 修改 flags
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        // 正常的類. 分配可寫的類數(shù)據(jù)。
        // 開(kāi)辟 rw 內(nèi)存空間
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
// 注: rw ro rwe
//
    }
    // 判斷是否是元類
#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    // 設(shè)置cls->instancesRequireRawIsa如果沒(méi)有更多的索引可用
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }
    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    // 遞歸調(diào)用巍佑,實(shí)現(xiàn)父類和元類
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        // 禁用一些類和非指針isa
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
        // 禁用非指針的 isa
        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            // 非指針isa禁用的環(huán)境或應(yīng)用程序SDK版本
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object"))
        {
            // 在 hackedDispatch 里 isa 也充當(dāng)虛表指針
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&
                 supercls->instancesRequireRawIsa())
        {
            // 從元類到根元類設(shè)置
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif
    // 在重新映射時(shí)更新父類和元類
    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // 協(xié)調(diào)實(shí)例變量的偏移量/布局茴迁,可能會(huì)重新分配 class_ro_t,更新我們的 ro 變量萤衰。
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // 如果還沒(méi)有設(shè)置就開(kāi)始設(shè)置 fastInstanceSize堕义。
    cls->setInstanceSize(ro->instanceSize);

    // 將一些標(biāo)志從 ro 復(fù)制到 rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    
    // 從ro或父類中傳播關(guān)聯(lián)的對(duì)象禁止標(biāo)志
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // 將這個(gè)類連接到它的父類的子類列表,即雙向綁定
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // 整理 cls 的方法列表脆栋、協(xié)議列表和屬性列表倦卖,以及附加任何未完成的類別
    methodizeClass(cls, previously);

    return cls;
}

(1.8.1) methodizeClass :方法的序列化

//方法的序列化
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    // 將 ro 里面的方法附加到 rw 里面去
    method_list_t *list = ro->baseMethods();
    if (list) {
// 準(zhǔn)備好方法列表
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    // 將 ro 里面的屬性附加到 rw 里面去
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    // 將 ro 里面的協(xié)議附加到 rw 里面去
    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // 根類獲得額外的方法實(shí)現(xiàn),如果它們還沒(méi)有椿争。這些適用于類別替換之前怕膛。
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }
    // Attach categories.  附加分類
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
    ......
}

對(duì)methodizeClass這個(gè)過(guò)程中attachLists方法進(jìn)行解析:

基本上方法、協(xié)議秦踪、屬性都是通過(guò) attachLists 函數(shù)附加到對(duì)應(yīng)的列表上的
void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        //計(jì)算數(shù)組中舊lists的大小
        uint32_t oldCount = array()->count;
        //計(jì)算新的容量大小 = 舊數(shù)據(jù)大小+新數(shù)據(jù)大小
        uint32_t newCount = oldCount + addedCount;
        //根據(jù)新的容量大小褐捻,開(kāi)辟一個(gè)數(shù)組,類型是 array_t椅邓,通過(guò)array()獲取
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        //設(shè)置數(shù)組大小
        array()->count = newCount;
        //舊的數(shù)據(jù)從 addedCount 數(shù)組下標(biāo)開(kāi)始 存放舊的lists柠逞,大小為 舊數(shù)據(jù)大小 * 單個(gè)舊list大小
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        //新數(shù)據(jù)從數(shù)組 首位置開(kāi)始存儲(chǔ),存放新的lists景馁,大小為 新數(shù)據(jù)大小 * 單個(gè)list大小
        memcpy(
               array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];//將list加入mlists的第一個(gè)元素边苹,此時(shí)的list是一個(gè)一維數(shù)組
    } 
    else {
        // 1 list -> many lists 有了一個(gè)list,有往里加很多l(xiāng)ist
        //新的list就是分類裁僧,來(lái)自LRU的算法思維个束,即最近最少使用
        //獲取舊的list
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        //計(jì)算容量和 = 舊list個(gè)數(shù)+新lists的個(gè)數(shù)
        uint32_t newCount = oldCount + addedCount;
        //開(kāi)辟一個(gè)容量和大小的集合,類型是 array_t聊疲,即創(chuàng)建一個(gè)數(shù)組茬底,放到array中,通過(guò)array()獲取
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        //設(shè)置數(shù)組的大小
        array()->count = newCount;
        //判斷old是否存在获洲,old肯定是存在的阱表,將舊的list放入到數(shù)組的末尾
        if (oldList) array()->lists[addedCount] = oldList;
        // memcpy(開(kāi)始位置,放什么贡珊,放多大) 是內(nèi)存平移最爬,從數(shù)組起始位置存入新的list
        //其中array()->lists 表示首位元素位置
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}

從源碼可以得知,插入表主要分為三種情況:

  • 情況1:多對(duì)多】如果當(dāng)前調(diào)用attachLists的list_array_tt二維數(shù)組中有多個(gè)一維數(shù)組

    • 計(jì)算數(shù)組中舊lists的大小

    • 計(jì)算新的容量大小 = 舊數(shù)據(jù)大小+新數(shù)據(jù)大小

    • 根據(jù)新的容量大小门岔,開(kāi)辟一個(gè)數(shù)組爱致,類型是 array_t,通過(guò)array()獲取

    • 設(shè)置數(shù)組大小

    • 舊的數(shù)據(jù)從 addedCount 數(shù)組下標(biāo)開(kāi)始 存放舊的lists寒随,大小為 舊數(shù)據(jù)大小 * 單個(gè)舊list大小糠悯, 即整段平移帮坚,可以簡(jiǎn)單理解為原來(lái)的數(shù)據(jù)移動(dòng)到后面,即指針偏移

    • 新數(shù)據(jù)從數(shù)組 首位置開(kāi)始存儲(chǔ)互艾,存放新的lists试和,大小為 新數(shù)據(jù)大小 * 單個(gè)list大小,可以簡(jiǎn)單理解為越晚加進(jìn)來(lái)纫普,越在前面阅悍,越在前面,調(diào)用時(shí)則優(yōu)先調(diào)用

  • 情況2:0對(duì)一】如果調(diào)用attachLists的list_array_tt二維數(shù)組為空且新增大小數(shù)目為 1

    • 直接賦值addedList的第一個(gè)list
  • 情況3:一對(duì)多】如果當(dāng)前調(diào)用attachLists的list_array_tt二維數(shù)組只有一個(gè)一維數(shù)組

    • 獲取舊的list

    • 計(jì)算容量和 = 舊list個(gè)數(shù)+新lists的個(gè)數(shù)

    • 開(kāi)辟一個(gè)容量和大小的集合昨稼,類型是 array_t溉箕,即創(chuàng)建一個(gè)數(shù)組,放到array中悦昵,通過(guò)array()獲取

    • 設(shè)置數(shù)組的大小

    • 判斷old是否存在肴茄,old肯定是存在的,將舊的list放入到數(shù)組的末尾

    • memcpy(開(kāi)始位置但指,放什么寡痰,放多大) 是內(nèi)存平移,從數(shù)組起始位置開(kāi)始存入新的list棋凳,其中array()->lists 表示首位元素位置

  • 針對(duì)情況3拦坠,這里的lists是指分類

這是日常開(kāi)發(fā)中,為什么子類實(shí)現(xiàn)父類方法會(huì)把父類方法覆蓋的原因

同理剩岳,對(duì)于同名方法贞滨,分類方法覆蓋類方法的原因

這個(gè)操作來(lái)自一個(gè)算法思維 LRU即最近最少使用,加這個(gè)newlist的目的是由于要使用這個(gè)newlist中的方法拍棕,這個(gè)newlist對(duì)于用戶的價(jià)值要高晓铆,即優(yōu)先調(diào)用

會(huì)來(lái)到1對(duì)多的原因 ,主要是有分類的添加绰播,即舊的元素在后面骄噪,新的元素在前面 ,究其根本原因主要是優(yōu)先調(diào)用category蠢箩,這也是分類的意義所在

(2). dyld要init image的時(shí)候链蕊,會(huì)產(chǎn)生_dyld_objc_notify_init通知。在runime中, 是通過(guò)load_images方法做回調(diào)響應(yīng)的谬泌。

  • load_images 函數(shù)是對(duì) load 方法的加載和調(diào)用滔韵。

(2.1) load_images 分析

/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);

void
load_images(const char *path __unused, const struct mach_header *mh)
{
 
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }
//1.判斷鏡像中是否有Load方法,沒(méi)有直接返回
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);
 // 2.查找所有的Load方法
    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        // 準(zhǔn)備加載方法
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
   // 3.調(diào)用所有Load方法
    call_load_methods();
}

(2.2) prepare_load_methods分析

準(zhǔn)備加載方法
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, I;

    runtimeLock.assertLocked();
    // 獲取非懶加載類列表
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 循環(huán)遍歷去加載非懶加載類的 load 方法到 loadable_classes
        // schedule:安排掌实,預(yù)定
        schedule_class_load(remapClass(classlist[i]));
    }
    // 獲取非懶加載分類列表
    category_t * const *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
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        // 如果本類沒(méi)有初始化就去初始化
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        
        // 循環(huán)遍歷去加載非懶加載分類的 load 方法到 loadable_categories
        // 和非懶加載類差不多陪蜻,就是數(shù)組不一樣
        add_category_to_loadable_list(cat);
    }
}

(2.2.1) schedule_class_load分析

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images 必須實(shí)現(xiàn)
    if (cls->data()->flags & RW_LOADED) return;
    // 常規(guī)操作,遞歸調(diào)用父類加載 load 方法
    schedule_class_load(cls->superclass);
    // 將 load 方法加載到 loadable_classes
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
  • 可以看出潮峦,是先去加載父類的囱皿,然后再加載本類勇婴,之后再去加載分類的 load 方法

(2.2.2)schedule_class_load分析

/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();
//    獲取 load 方法的 imp
    method = cls->getLoadMethod();
//    // 如果沒(méi)有 load 方法直接返回
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    // 擴(kuò)容
    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++;
}

(2.3) 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.
    // 保證只調(diào)用一次
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();
    // do while 循環(huán)調(diào)用 load 方法
    do {
        // 1.重復(fù)調(diào)用非懶加載類的 load忱嘹,直到?jīng)]有更多的
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2.調(diào)用非懶加載分類的 load 方法嘱腥,和非懶加載類差不多
        more_categories = call_category_loads();

        // 3. 如果有類或更多未嘗試的類別,則運(yùn)行更多 load
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

(2.3.1) call_class_loads 分析

static void call_class_loads(void)
{
    int I;
    
    // Detach current loadable list.
    // 取出 loadable_classes
    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.
    // 調(diào)用保存在 loadable_classes 里的 load 方法
    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());
        }
        // 發(fā)送 load 消息
        (*load_method)(cls, @selector(load));
    }
    
    // 釋放內(nèi)存
    if (classes) free(classes);
}

可以看出load 的調(diào)用順序是:父類->本類->分類拘悦。

load_images方法整體調(diào)用過(guò)程


(3). 當(dāng)dyld要將image移除內(nèi)存時(shí)齿兔,會(huì)發(fā)送_dyld_objc_notify_unmapped通知。在runtime中础米,是用unmap_image方法來(lái)響應(yīng)的分苇。

unmap_image是用來(lái)處理將被dyld取消映射的給定images

void 
unmap_image(const char *path __unused, const struct mach_header *mh)
{
    recursive_mutex_locker_t lock(loadMethodLock);
    mutex_locker_t lock2(runtimeLock);
    unmap_image_nolock(mh);
}

void 
unmap_image_nolock(const struct mach_header *mh)
{
    if (PrintImages) {
        _objc_inform("IMAGES: processing 1 newly-unmapped image...\n");
    }

    header_info *hi;
    
    // Find the runtime's header_info struct for the image
    // 查找映像的運(yùn)行時(shí) header_info 結(jié)構(gòu)
    for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        if (hi->mhdr() == (const headerType *)mh) {
            break;
        }
    }

    if (!hi) return;

    if (PrintImages) {
        _objc_inform("IMAGES: unloading image for %s%s%s\n", 
                     hi->fname(),
                     hi->mhdr()->filetype == MH_BUNDLE ? " (bundle)" : "",
                     hi->info()->isReplacement() ? " (replacement)" : "");
    }
    // 目前只處理 MH_BUNDLE
    _unload_image(hi);

    // Remove header_info from header list
    // 從標(biāo)題列表中刪除 header_info
    removeHeader(hi);
    free(hi);
}

三 屁桑、總結(jié)

以上分析了_dyld_objc_notify_register這個(gè)在dyld和runtime中的跨庫(kù)通知医寿,在runtime中,分析了map_images蘑斧、load_images靖秩、unmap_image方法實(shí)現(xiàn)

_read_images幾個(gè)重要的方法:

  • readClass :通過(guò) readClass 函數(shù)獲取處理后的新類,內(nèi)部主要操作 ro 和 rw 結(jié)構(gòu)體
  • realizeClassWithoutSwift:實(shí)現(xiàn)所有非懶加載的類
  • methodizeClass:方法的序列化
  • attachLists:方法竖瘾、協(xié)議沟突、屬性基本上都是通過(guò) 這個(gè)方法 函數(shù)附加到對(duì)應(yīng)的列表上的

總結(jié)1 :map_images下的_read_images方法執(zhí)行流程

  • 1.map_images加載鏡像文件,_read_images讀取鏡像文件并加載類捕传, 通過(guò)_getObjc2NonlazyClassList獲取Mach-O的靜態(tài)段__objc_nlclslist非懶加載類表剪廉。
  • 2.第一次調(diào)用_read_images 的時(shí)候會(huì)去初始化兩張表 gdb_objc_realized_classesallocatedClasses 進(jìn)行類信息的存儲(chǔ)耗拓。
  • 3.初始化表之后就是調(diào)用readClass是將類插入到 allocatedClasses 表中的。
  • 4.然后再通過(guò)remapClassRef 進(jìn)行修復(fù)類的重映射和fixupMessageRef修復(fù)舊的函數(shù)指針調(diào)用遺留。
  • 5.類處理完之后就是添加協(xié)議和方法都注冊(cè)到哈希表中姓迅,方便以后對(duì)其進(jìn)行調(diào)用。
  • 6.再通過(guò) realizeClassWithoutSwift去初始化類它掂,其中是對(duì)類的rw實(shí)例化莫秆,以及將ro 數(shù)據(jù)賦值到rw 上。

總結(jié)2:load_images方法執(zhí)行流程

  • 1.判斷是否已經(jīng)初始化分類催什,如果沒(méi)有加載所有分類涵亏;
  • 2.判斷鏡像中是否有Load方法,沒(méi)有直接返回蒲凶,通過(guò)hasLoadMethods函數(shù)完成气筋;
  • 3.查找所有的Load方法,通過(guò)prepare_load_methods函數(shù)完成旋圆;
  • 4.調(diào)用所有Load方法宠默,通過(guò)call_load_methods函數(shù)完成。

load_images是對(duì) load 方法的處理以及調(diào)用灵巧,調(diào)用順序是 父類->本類->分類搀矫,而有多個(gè)分類 load 的時(shí)候是根據(jù)編譯順序執(zhí)行的

總結(jié)3:unmap_image方法執(zhí)行流程

unmap_image 是用來(lái)處理將被 dyld 取消映射的給定 images

在本篇文章中抹沪,我們知道dyld在main()函數(shù)之前,會(huì)調(diào)用runtime的_objc_init 方法瓤球。_objc_init是runtime的入口函數(shù)融欧,通過(guò)跨庫(kù)通知_dyld_objc_notify_register,實(shí)現(xiàn)類的加載卦羡,使dyld和objc關(guān)聯(lián)噪馏。在上文提到過(guò)類的懶加載和非懶加載,下篇文章分析下绿饵,類在懶加載和非懶加載兩種情況下有啥不同欠肾。

四 、拓展:關(guān)于class_ro_tclass_rw_t

類的結(jié)構(gòu)以前分析過(guò)的

struct objc_class : objc_object {   
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ........
 struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

  .........
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    ...........

class_ro_t存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性拟赊、方法以及遵循的協(xié)議刺桃,里面是沒(méi)有分類的方法的。那些運(yùn)行時(shí)添加的方法將會(huì)存儲(chǔ)在運(yùn)行時(shí)生成的class_rw_t中吸祟。

ro即表示read only瑟慈,是無(wú)法進(jìn)行修改的。

ObjC 類中的屬性欢搜、方法還有遵循的協(xié)議等信息都保存在 class_rw_t

class_rw_t生成時(shí)機(jī)

class_rw_t生成在運(yùn)行時(shí)封豪,在編譯期間,class_ro_t結(jié)構(gòu)體就已經(jīng)確定炒瘟,objc_class中的bits的data部分存放著該結(jié)構(gòu)體的地址吹埠。在runtime運(yùn)行之后,具體說(shuō)來(lái)是在運(yùn)行runtime的realizeClass 方法時(shí)疮装,會(huì)生成class_rw_t結(jié)構(gòu)體缘琅,該結(jié)構(gòu)體包含了class_ro_t,并且更新data部分廓推,換成class_rw_t結(jié)構(gòu)體的地址刷袍。

區(qū)別:

class_ro_t存放的是編譯期間就確定的;
class_rw_t是在runtime時(shí)才確定樊展,它會(huì)先將class_ro_t的內(nèi)容拷貝過(guò)去呻纹,然后再將當(dāng)前類的分類的這些屬性、方法等拷貝到其中专缠。
所以可以說(shuō)class_rw_tclass_ro_t的超集雷酪,當(dāng)然實(shí)際訪問(wèn)類的方法、屬性等也都是訪問(wèn)的class_rw_t中的內(nèi)容

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涝婉,一起剝皮案震驚了整個(gè)濱河市哥力,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖吩跋,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寞射,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡锌钮,警方通過(guò)查閱死者的電腦和手機(jī)桥温,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)轧粟,“玉大人策治,你說(shuō)我怎么就攤上這事脓魏±家鳎” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵茂翔,是天一觀的道長(zhǎng)混蔼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)珊燎,這世上最難降的妖魔是什么惭嚣? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮悔政,結(jié)果婚禮上晚吞,老公的妹妹穿的比我還像新娘。我一直安慰自己谋国,他們只是感情好槽地,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著芦瘾,像睡著了一般捌蚊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上近弟,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天缅糟,我揣著相機(jī)與錄音,去河邊找鬼祷愉。 笑死窗宦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的二鳄。 我是一名探鬼主播赴涵,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泥从!你這毒婦竟也來(lái)了句占?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤躯嫉,失蹤者是張志新(化名)和其女友劉穎纱烘,沒(méi)想到半個(gè)月后杨拐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡擂啥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年哄陶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哺壶。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屋吨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出山宾,到底是詐尸還是另有隱情至扰,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布资锰,位于F島的核電站敢课,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏绷杜。R本人自食惡果不足惜直秆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鞭盟。 院中可真熱鬧圾结,春花似錦、人聲如沸齿诉。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鹃两。三九已至遗座,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俊扳,已是汗流浹背途蒋。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馋记,地道東北人号坡。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像梯醒,于是被迫代替她去往敵國(guó)和親宽堆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355