iOS探索 類的加載過程

歡迎閱讀iOS探索系列(按序閱讀食用效果更加)c++

寫在前面

在上一篇文章iOS探索 淺嘗輒止dyld加載流程輕描淡寫提了一句_objc_init_dyld_objc_notify_register,本文將圍繞它展開探索分析swift

1、_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?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();
    // 保存 - libobjc - dyld
    // C++ 怎麼去作到通知
    // 指針 - 回調 - 函數的地址
    
    // 這裏就是咱們的數據 - images - objc lib
    // dyld
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼

1.environ_init方法

environ_init()方法是初始化一系列環境變量,並讀取影響運行時的環境變量數組

// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
if (PrintHelp  ||  PrintOptions) {
    if (PrintHelp) {
        _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
        _objc_inform("OBJC_HELP: describe available environment variables");
        if (PrintOptions) {
            _objc_inform("OBJC_HELP is set");
        }
        _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
    }
    if (PrintOptions) {
        _objc_inform("OBJC_PRINT_OPTIONS is set");
    }

    for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
        const option_t *opt = &Settings[i];            
        if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
        if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
    }
}
複製代碼

經過上述源碼中的判斷條件,咱們能夠獲得一些環境變量的描述信息 緩存

  • OBJC_PRINT_LOAD_METHODS能夠監控全部的+load方法,從而處理啓動優化
  • OBJC_DISABLE_NONPOINTER_ISA能夠控制isa優化開關,從而優化整個內存結構
  • 更多環境變量請終端輸出export OBJC_HELP=1查看

2.tls_init方法

tls_init()方法是關於線程key的綁定安全

void tls_init(void) {
#if SUPPORT_DIRECT_THREAD_KEYS
    _objc_pthread_key = TLS_DIRECT_KEY;
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
複製代碼

3.static_init方法

static_init()方法註釋中提到該方法會運行C++靜態構造函數(只會運行系統級別的構造函數)app

在dyld調用靜態構造函數以前libc會調用_objc_init,因此必須本身去實現函數

/*********************************************************************** * static_init * Run C++ static constructor functions. * libc calls _objc_init() before dyld would call our static constructors, * so we have to do it ourselves. **********************************************************************/
static void static_init() {
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
}
複製代碼

4.lock_init方法

lock_init()方法是個空函數,OC的鎖機制徹底採用C、C++那一套oop

void lock_init(void) {
}
複製代碼

5.exception_init方法

exception_init()初始化libobjc的異常處理系統,註冊異常處理的回調,從而監控異常的處理post

void exception_init(void) {
    old_terminate = std::set_terminate(&_objc_terminate);
}
複製代碼

調用只聲明不實現不做任何處理的方法,就會報錯,來到_objc_terminate 優化

6._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.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);
複製代碼

_dyld_objc_notify_register方法的註釋中能夠得出:

  • 僅供objc運行時使用
  • 註冊處理程序,以便在映射、取消映射和初始化objc圖像時調用
  • dyld將會經過一個包含objc-image-info的鏡像文件的數組回調mapped函數

_dyld_objc_notify_register中的三個參數含義以下:

  • map_images:dyld將image加載進內存時,會觸發該函數
  • load_image:dyld初始化image會觸發該函數
  • unmap_image:dyld將image移除時,會觸發該函數

2、map_images->_read_images

當鏡像加載到內存時map_image會觸發

/*********************************************************************** * map_images * Process the given images which are being mapped in by dyld. * Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: write-locks runtimeLock **********************************************************************/
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_image調用map_images_nolock,其中hCount表示鏡像文件的個數,調用_read_images來加載鏡像文件

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

    firstTime = NO;
}
複製代碼

經過以上這些能夠得出最核心的邏輯都在_read_images函數

1.建立表

經過doneOnce一次建立兩張表gdb_objc_realized_classesallocatedClasses

if (!doneOnce) {
    doneOnce = YES;
    
    ...
    
    // namedClasses
    // Preoptimized classes don't go in this table.
    // 4/3 is NXMapTable's load factor
    int namedClassesSize = 
        (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
    gdb_objc_realized_classes =
        NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
    
    allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
    
    ts.log("IMAGE TIMES: first time tasks");
}
複製代碼
  • gdb_objc_realized_classes存儲不在共享緩存且已命名的全部類,其容量是類數量的4/3
  • allocatedClasses存儲已經初始化的類
// 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.
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;
複製代碼

2.類的重映射

從列表中取出全部類,遍歷進行處理

for (EACH_HEADER) {
    // 從編譯後的類列表中取出全部類,獲取到的是一個classref_t類型的指針
    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++) {
         // 數組中會取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系統類,例如CF、Fundation、libdispatch中的類。以及本身建立的類
        Class cls = (Class)classlist[i];
        
        // 經過readClass函數獲取處理後的新類,
        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方法會返回Class,跟進去看看具體實現(把目光放在全部返回值上)

  • 當前類的父類中如有丟失的weak-linked類,則返回nil

  • 正常狀況下不會走進popFutureNamedClass判斷,這是專門針對將來的待處理的類的特殊操做,所以也不會對ro、rw進行操做(可打斷點調試,建立類和系統類都不會進入)
  • 在調用addNamedClassaddClassTableEntry方法後返回cls

將當前類添加到已建立好的gdb_objc_realized_classes哈希表(存放全部類)

static void addNamedClass(Class cls, const char *name, Class replacing = nil) {
    runtimeLock.assertLocked();
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

        // getMaybeUnrealizedNonMetaClass uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    assert(!(cls->data()->flags & RO_META));

    // wrong: constructed classes are already realized when they get here
    // assert(!cls->isRealized());
}
複製代碼

當前類已經初始化,因此要添加到allocatedClasses哈希表

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

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    assert(!NXHashMember(allocatedClasses, cls));

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

3.修復重映射

將未映射Class和Super Class重映射,調用_getObjc2ClassRefs獲取類的引用,調用_getObjc2SuperRefs獲取父類的引用,經過remapClassRef進行重映射

// 將未映射Class和Super Class重映射,被remap的類都是非懶加載的類
if (!noClassesRemapped()) {
    for (EACH_HEADER) {
        // 重映射Class,注意是從_getObjc2ClassRefs函數中取出類的引用
        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]);
        }
    }
}
複製代碼

4.添加SEL到namedSelectors表

經過_getObjc2SelectorRefs拿到MachO中的靜態段__objc_selrefs,遍歷列表調用sel_registerNameNoLock將SEL添加到namedSelectors哈希表

// 將全部SEL都註冊到哈希表中,是另一張哈希表
// 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]);
            // 註冊SEL的操做
            sels[i] = sel_registerNameNoLock(name, isBundle);
        }
    }
}
複製代碼

5.修復舊的函數指針調用遺留

經過_getObjc2MessageRefs獲取到靜態段__objc_selrefsfixupMessageRef遍歷將函數指針進行註冊,並fix爲新的函數指針

// Fix up old objc_msgSend_fixup call sites
// 修復舊的函數指針調用遺留
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++) {
         // 內部將經常使用的alloc、objc_msgSend等函數指針進行註冊,並fix爲新的函數指針
        fixupMessageRef(refs+i);
    }
}
複製代碼

6.添加Protocol到協議表

調用_getObjc2ProtocolList獲取到__objc_protolist協議列表,readProtocol遍歷添加Protocol到protocol_map哈希表

// Discover protocols. Fix up protocol refs.
// 遍歷全部協議列表,而且將協議列表加載到Protocol的哈希表中
for (EACH_HEADER) {
    extern objc_class OBJC_CLASS_$_Protocol;
    // cls = Protocol類,全部協議和對象的結構體都相似,isa都對應Protocol類
    Class cls = (Class)&OBJC_CLASS_$_Protocol;
    assert(cls);
    // 獲取protocol哈希表
    NXMapTable *protocol_map = protocols();
    bool isPreoptimized = hi->isPreoptimized();
    bool isBundle = hi->isBundle();

    // 從編譯器中讀取並初始化Protocol
    protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
    for (i = 0; i < count; i++) {
        readProtocol(protolist[i], cls, protocol_map, 
                     isPreoptimized, isBundle);
    }
}
複製代碼

7.修復協議列表引用

經過_getObjc2ProtocolRefs獲取到__objc_protorefs**(與__objc_protolist不是同一個東西)**遍歷remapProtocolRef修復協議,remapProtocolRef比較當前協議和協議列表中同一內存地址的協議是否相同,若是不一樣則替換

// Fix up @protocol references
// Preoptimized images may have the right 
// answer already but we don't know for sure.
// 修復協議列表引用,優化後的images多是正確的,可是並不肯定
for (EACH_HEADER) {
    // 須要注意到是,下面的函數是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不同
    protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
    for (i = 0; i < count; i++) {
        remapProtocolRef(&protolist[i]);
    }
}
複製代碼

8.實現非懶加載的類

蘋果官方對於非懶加載類的定義是:

NonlazyClass is all about a class implementing or not a +load method.

因此實現了+load方法的類是非懶加載類,不然就是懶加載類

下面是非懶加載類的加載流程:

  • _getObjc2NonlazyClassList獲取到__objc_nlclslist,取出非懶加載類
  • addClassTableEntry再加載一遍——若是已添加就不會添加進去,確保整個結構都被添加
  • realizeClassWithoutSwift是接下來要關注的地方
// Realize non-lazy classes (for +load methods and static instances)
// 實現非懶加載的類,對於load方法和靜態實例變量
for (EACH_HEADER) {
    classref_t *classlist = 
        _getObjc2NonlazyClassList(hi, &count);
    for (i = 0; i < count; i++) {
        Class cls = remapClass(classlist[i]);
        // printf("non-lazy Class:%s\n",cls->mangledName());
        if (!cls) continue;

        // hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATOR
        if (cls->cache._buckets == (void*)&_objc_empty_cache  &&  
            (cls->cache._mask  ||  cls->cache._occupied)) 
        {
            cls->cache._mask = 0;
            cls->cache._occupied = 0;
        }
        if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache  &&  
            (cls->ISA()->cache._mask  ||  cls->ISA()->cache._occupied)) 
        {
            cls->ISA()->cache._mask = 0;
            cls->ISA()->cache._occupied = 0;
        }
#endif
        
        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
        }
        // 實現全部非懶加載的類(實例化類對象的一些信息,例如rw)
        realizeClassWithoutSwift(cls);
    }
}
複製代碼

realizeClassWithoutSwift分析:

rw初始化並將ro拷貝一份到rw中的ro

  • rw表示readWrite,因爲動態性,可能會往類中添加屬性、方法、添加協議
  • ro表示readOnly,在編譯時已經肯定了內存
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
    // This was a future class. rw data is already allocated.
    rw = cls->data();
    ro = cls->data()->ro;
    cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
    // Normal class. Allocate writeable class data.
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    rw->ro = ro;
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);
}
複製代碼

②遞歸調用realizeClassWithoutSwift完善繼承鏈並處理當前類的父類元類;若是有父類,就經過addSubclass當前類放到父類子類列表中去

if (!cls) return nil;
...
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
...
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
...
// Connect this class to its superclass's subclass lists
if (supercls) {
    addSubclass(supercls, cls);
} else {
    addRootClass(cls);
}
複製代碼

③當isa找到根元類以後,根元類的isa是指向本身的,不會返回nil從而致使死循環——remapClass中對類在表中進行查找的操做,若是表中已有該類,則返回一個空值;若是沒有則返回當前類,這樣保證了類只加載一次並結束遞歸

static Class remapClass(Class cls) {
    runtimeLock.assertLocked();

    Class c2;

    if (!cls) return nil;

    NXMapTable *map = remappedClasses(NO);
    if (!map  ||  NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) {
        return cls;
    } else {
        return c2;
    }
}
複製代碼

④最後調用了methodizeClass

// Attach categories
methodizeClass(cls);
return cls;
複製代碼

⑤在methodizeClass中,從ro讀取方法列表(包括分類中的方法)、屬性列表、協議列表賦值給rw

// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
    prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
    rw->methods.attachLists(&list, 1);
}

property_list_t *proplist = ro->baseProperties;
if (proplist) {
    rw->properties.attachLists(&proplist, 1);
}

protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
    rw->protocols.attachLists(&protolist, 1);
}

// Root classes get bonus method implementations if they don't have 
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
    // root metaclass
    addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}

// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
複製代碼

attachLists是如何插入數據的呢?方法屬性協議均可以直接經過attachLists插入嗎?

方法、屬性繼承於entsize_list_tt協議則是相似entsize_list_tt實現,都是二維數組

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

    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;//10
        uint32_t newCount = oldCount + addedCount;//4
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;// 10+4

        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]));
    }
}
複製代碼

attachLists的源碼實現中能夠得出:

  • (多對多)若是當前調用attachListslist_array_tt二維數組中有多個一維數組

    • 經過realloc對容器進行從新分配大小爲原來的大小加上新增的大小
    • 經過memmove把原來的數據移動到容器的末尾
    • 把新的數據memcpy拷貝到容器的起始位置
  • (0對一)若是調用attachListslist_array_tt二維數組爲空且新增大小數目爲 1

    • 直接賦值addedList的第一個list
  • (一對多)若是當前調用attachListslist_array_tt二維數組只有一個一維數組

    • 經過realloc對容器進行從新分配大小爲原來的大小加上新增的大小
    • 因爲只有一個一維數組,因此直接賦值到新Array的最後一個位置
    • 把新的數據memcpy拷貝到容器的起始位置

memmovememcpy的區別在於:

  • 在不知道須要平移的內存大小時,須要memmove進行內存平移,保證安全
  • memcpy從原內存地址的起始位置開始拷貝若干個字節到目標內存地址中,速度快

9.實現懶加載類

前面已經提到了實現+load方法的類就是非懶加載類,那麼沒有實現的類就是懶加載類

也能夠經過printf("non-lazy Class:%s\n",cls->mangledName())去打印獲取到全部非懶加載類,發現只有實現了+load的類纔會被打印(FXPerson內部實現了+load,其餘都是系統內置的類)

那麼懶加載類是什麼時候加到內存中的呢?

之因此叫懶加載類還不是由於它懶嘛😆,用到的時候纔會加到內存中

調用懶加載類讓他幹活就是發送消息,仔細閱讀lookUpImpOrForward實現發現會有這麼一頓操做

  • 類沒有被加載時,調用realizeClassMaybeSwiftAndLeaveLocked
  • realizeClassMaybeSwiftAndLeaveLocked調用realizeClassMaybeSwiftMaybeRelock
  • realizeClassMaybeSwiftMaybeRelock調用realizeClassWithoutSwift
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) {
    ...
    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    ...
}

static Class realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock) {
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}

static Class realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked) {
    lock.assertLocked();

    if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
        // Non-Swift class. Realize it now with the lock still held.
        // fixme wrong in the future for objc subclasses of swift classes
        realizeClassWithoutSwift(cls);
        if (!leaveLocked) lock.unlock();
    } else {
        // Swift class. We need to drop locks and call the Swift
        // runtime to initialize it.
        lock.unlock();
        cls = realizeSwiftClass(cls);
        assert(cls->isRealized());    // callback must have provoked realization
        if (leaveLocked) lock.lock();
    }

    return cls;
}
複製代碼

Q1:實現了+load的子類ClassA繼承於沒有+load的父類ClassBClassA屬於非懶加載類,在_read_images時加載。那麼ClassB是什麼呢?什麼時候加載?

A1ClassB屬於懶加載類,在子類realizeClassWithoutSwift遞歸時加載到內存(前文有提到realizeClassWithoutSwift會完善繼承鏈並處理當前類的父類、元類

Q2:父類A實現了+load方法,子類B沒有實現,那麼子類B是懶加載類?什麼時候加載?

A2子類B屬於懶加載類父類A幹活跟子類B不要緊,用到時再加載

10.發現和處理全部Category

因爲篇幅有限,將在下一篇文章中介紹

// Discover categories.
// 發現和處理全部Category
for (EACH_HEADER) {
    // 外部循環遍歷找到當前類,查找類對應的Category數組
    category_t **catlist = 
        _getObjc2CategoryList(hi, &count);
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    for (i = 0; i < count; i++) {
        // 內部循環遍歷當前類的全部Category
        category_t *cat = catlist[i];
        Class cls = remapClass(cat->cls);
        
        // 首先,經過其所屬的類註冊Category。若是這個類已經被實現,則從新構造類的方法列表。
        bool classExists = NO;
        if (cat->instanceMethods ||  cat->protocols  
            ||  cat->instanceProperties) 
        {
            // 將Category添加到對應Class的value中,value是Class對應的全部category數組
            addUnattachedCategoryForClass(cat, cls, hi);
            // 將Category的method、protocol、property添加到Class
            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" : "");
            }
        }

        // 這塊和上面邏輯同樣,區別在於這塊是對Meta Class作操做,而上面則是對Class作操做
        // 根據下面的邏輯,從代碼的角度來講,是能夠對原類添加Category的
        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);
            }
        }
    }
}
複製代碼

寫在後面

本文主要講了MachO中的數據是如何加載到內存的,有些細節點須要本身LLVM調試纔會有所體會

下一篇文章會梳理分類的加載load_image流程

相關文章
相關標籤/搜索