類、分類的加載

上篇文章應(yīng)用程序加載分析了dyldmain()函數(shù)的大體流程,這篇文章主要分析_objc_init()到類的處理流程。

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?
    // 讀取影響運(yùn)行時(shí)的環(huán)境變量,源碼工程中可打印。
    environ_init();
    // 關(guān)于線程key的綁定 比如線程數(shù)據(jù)的析構(gòu)函數(shù)
    tls_init();
    // 運(yùn)行 C++ 靜態(tài)構(gòu)造函數(shù)。在dyld調(diào)用我們的靜態(tài)構(gòu)造函數(shù)之前,libc會(huì)調(diào)用 _objc_init(),因此需要自己做
    static_init();
    // runtime運(yùn)行時(shí)環(huán)境初始化,主要是unattachedCategories、allocatedClasses方法
    runtime_init();
    // 初始化 libObjc的異常處理系統(tǒng)
    exception_init();
    // 緩存條件初始化
    cache_init();
    // 啟動(dòng)回調(diào)機(jī)制,通常不會(huì)做什么,因?yàn)樗械某跏蓟际嵌栊缘模菍?duì)于某些進(jìn)程我們會(huì)迫不及待的加載trampolines dylib。
    _imp_implementationWithBlock_init();
    // 注冊(cè)回調(diào) map_images 管理文件和動(dòng)態(tài)庫(kù)中的所有符號(hào)(class Protocol selector category) load_image 加載執(zhí)行l(wèi)oad方法
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

環(huán)境變量可通過(guò)修改源碼工程代碼打?。?/p>

void environ_init(void) 
{
....省略
    for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
        const option_t *opt = &Settings[i];
        _objc_inform("%s: %s", opt->env, opt->help);
        _objc_inform("%s is set", opt->env);
    }
}

打印結(jié)果:


environ_init1.png

OBJC_PRINT_LOAD_METHODS 設(shè)置了會(huì)打印所有執(zhí)行+(void)load方法的類,舉個(gè)??

environ_init2.png

environ_init3.png

2._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);

通過(guò)注釋可知:

  • 僅供objc 運(yùn)行時(shí)使用。
  • 注冊(cè)回調(diào),當(dāng)鏡像文件被mapped--映射--&map_images、unmapped--取消映射--unmap_imageinit--初始化--load_images。

先看下map_images方法,通過(guò)源碼分析可知其核心方法為map_images->map_images_nolock-> _read_images,源碼如下:

/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked 
* list beginning with headerList. 
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
    // 1.控制一次性加載條件
    if (!doneOnce) {
    ...
            // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        // 創(chuàng)建哈希表 儲(chǔ)存不在共享緩存且已命名的類,不論是否實(shí)現(xiàn),容量為類容量的3/4.
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
    ...
    }
    // Fix up @selector references
    // 2.修復(fù)預(yù)編譯階段@selector錯(cuò)亂問(wèn)題 sel是帶地址的字符串
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            // 拿到Mach-O中的靜態(tài)段__objc_selrefs
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                // 注冊(cè) sel
                SEL sel = sel_registerNameNoLock(name, isBundle);
                // 如果地址不相同改為一致
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
...
 // 3、錯(cuò)誤混亂的類處理
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }
        // 從Mach-O中獲取靜態(tài)段__objc_classlist
        classref_t const *classlist = _getObjc2ClassList(hi, &count);

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

        for (i = 0; i < count; i++) {
            // 此時(shí)類只是一個(gè)地址
            Class cls = (Class)classlist[i];
            // 變?yōu)轭惷?地址
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
            // 經(jīng)過(guò)調(diào)試沒(méi)有進(jìn)入
            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.
                // 將懶加載類添加到數(shù)組
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }
...
    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    // 4、修復(fù)重映射一些沒(méi)有被鏡像文件加載進(jìn)來(lái)的類 經(jīng)調(diào)試沒(méi)有進(jìn)入
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            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]);
            }
        }
    }
...
    // 5、修復(fù)一些消息
    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++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
...
   // 6、當(dāng)類里面有協(xié)議時(shí):readProtocol 讀取協(xié)議 遍歷尋找所有的協(xié)議列表添加到protocol_map哈希表
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->hasPreoptimizedProtocols();

        // Skip reading protocols if this is an image from the shared cache
        // and we support roots
        // Note, after launch we do need to walk the protocol as the protocol
        // in the shared cache is marked with isCanonical() and that may not
        // be true if some non-shared cache binary was chosen as the canonical
        // definition
        if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }

        bool isBundle = hi->isBundle();
        // 獲取到Mach-O中的靜態(tài)段__objc_protolist協(xié)議列表,存入protocol_map表
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }
... 
    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    //7、修復(fù)沒(méi)有被加載的協(xié)議
    for (EACH_HEADER) {
        // 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;
        // 獲取到Mach-O的靜態(tài)段 __objc_protorefs
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            // 比較協(xié)議內(nèi)存地址是否相同,不同則替換
            remapProtocolRef(&protolist[i]);
        }
    }
...
    // 8、分類處理
    // Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
    // 需要在分類初始化將數(shù)據(jù)加載到類后才執(zhí)行,對(duì)于運(yùn)行時(shí)出現(xiàn)的分類,將分類的發(fā)現(xiàn)推遲到對(duì)_dyld_objc_notify_register調(diào)用完成后地方第一個(gè)load_images調(diào)用為止
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }
...
    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    // Realize non-lazy classes (for +load methods and static instances)
    // 9、類的加載處理
    for (EACH_HEADER) {
        // 獲取Mach-O的靜態(tài)段__objc_nlclslist非懶加載類表
        classref_t const *classlist =
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            // 插入當(dāng)前類
            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)類上部分的cls為類名+地址但是類數(shù)據(jù)并沒(méi)初始化。
            realizeClassWithoutSwift(cls, nil);
        }
    }
...
    // 10、沒(méi)有被處理的類,優(yōu)化那些被侵犯的類
    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            // 實(shí)現(xiàn)類
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    ts.log("IMAGE TIMES: realize future classes");

    if (DebugNonFragileIvars) {
        // 實(shí)現(xiàn)所有類
        realizeAllClasses();
    }
...
}

第三步的readClass方法,走過(guò)之后打印

readClass打印.png

看下源碼:

/***********************************************************************
* 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();
    //當(dāng)前類的父類中若有丟失的weak-linked類,則返回nil
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    // 判斷是否是未來(lái)要處理的類(斷點(diǎn)后發(fā)現(xiàn)不會(huì)走)
    if (Class newCls = popFutureNamedClass(mangledName)) {
    ... ro rw處理
}
    // 判斷類是否已經(jīng)加載到內(nèi)存
    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 {
        addNamedClass(cls, mangledName, replacing); // 加載到共享緩存中
        addClassTableEntry(cls);        // 插入表  machO-->內(nèi)存
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}

mangledName方法源碼:

    const char *mangledName() { 
        // fixme can't assert locks here
        ASSERT(this);
        // 已經(jīng)實(shí)現(xiàn)或者未來(lái)類,類名從rw->ro()->name
        if (isRealized()  ||  isFuture()) {
            return data()->ro()->name;
        } else {
            //從ro中獲取
            return ((const class_ro_t *)data())->name;
        }
    }

addNamedClass源碼分析:

/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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 {
        // 插入到gdb_objc_realized_classes哈希表 在runtimeinit()方法中創(chuàng)建
        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());
}

獲取到類名之后把類名插入gdb_objc_realized_classes哈希表。

gdb_objc_realized_classes.png

readClass總結(jié): 只是把類從machO取出讀到內(nèi)存,插入表中,獲取到的類為地址 + 名字。

realizeClassWithoutSwift 方法

realizeClassWithoutSwift主要是下面三個(gè)步驟:
1.處理data(),將data()---ro替換為rw,并把ro拷貝到rw -->ro
2.遞歸調(diào)用realizeClassWithoutSwift完善類結(jié)構(gòu)和繼承連關(guān)系。
3.methodizeClass處理方法。

1 處理data()數(shù)據(jù)

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; // 如果是已實(shí)現(xiàn)類(data()讀出來(lái)是rw) 直接返回
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
    // 取出類data()此時(shí)為class_ro_t
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META; //是否是元類
    if (ro->flags & RO_FUTURE) {  // 是否是未來(lái)類
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>(); //rw分配空間
        rw->set_ro(ro); //設(shè)置rw中的ro
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta; // 設(shè)置flag  RW_REALIZED class_t->data 是rw非ro  RW_REALIZING 類已經(jīng)實(shí)現(xiàn)但未完成
        cls->setData(rw); //data()設(shè)置為rw
    }
...
  • roreadOnly在編譯時(shí)確定了內(nèi)存,包含類名、方法、協(xié)議、和實(shí)例變量等信息,由于是只讀的,被稱為clean Memory,值加載后不會(huì)發(fā)生改變的內(nèi)存。
  • rw表示readWrite,由于動(dòng)態(tài)性,可能會(huì)往類中添加屬性,方法、協(xié)議,在2020WWDC中關(guān)于內(nèi)存優(yōu)化提到rw,只有10%的類改變了rw,所以有的rwe,類的額外可讀可寫結(jié)構(gòu),rw為可讀可寫,所有為dirty Memory,運(yùn)行時(shí)可能會(huì)添加方法或者屬性等。

2.遞歸調(diào)用 realizeClassWithoutSwift 完善類結(jié)構(gòu)和繼承連關(guān)系。

// 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)用 確定繼承連關(guān)系 實(shí)現(xiàn)父類和元類  remapClass中當(dāng)找到NSObject返回nil打破循環(huán)
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    // 根元類isa指向自身 通過(guò)if (cls->isRealized()) return cls 打破循環(huán)。
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    ...
        // 完善類結(jié)構(gòu) 賦值父類和isa指向元類
    cls->superclass = supercls;
    cls->initClassIsa(metacls);
    ....
        // Connect this class to its superclass's subclass lists
    if (supercls) {
        // 雙向綁定父類子類關(guān)系
        addSubclass(supercls, cls);
    } else {
        // 綁定NSObjcet或者別的沒(méi)有父類的根類,父類指向nil
        addRootClass(cls);
    }
    ....

遞歸調(diào)用realizeClassWithoutSwift 循環(huán)設(shè)置父類元類類,經(jīng)測(cè)試最先走的類是NSObject元類,然后是NSObject類。

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

    if (!cls) return nil;

    auto *map = remappedClasses(NO);
    if (!map)
        return cls;
    
    auto iterator = map->find(cls);
    if (iterator == map->end())
        return cls;
    return std::get<1>(*iterator);
}

3.methodizeClass

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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.
    // 添加方法列表、屬性列表、協(xié)議列表到rw中
    // 方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls)); //根據(jù)sel地址,對(duì)方法列表排序
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    // 屬性列表
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    // 協(xié)議列表
    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->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 根元類添加initialize方法
        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);
...
}

methodizeClass方法主要是為添加方法、屬性、協(xié)議,當(dāng)當(dāng)前調(diào)用者為類的時(shí)候,只會(huì)對(duì)rw->ro()的方法進(jìn)行排序(按照sel地址),此時(shí)取出的是ro,不是rwe。

attachToClass方法

    void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        auto it = map.find(previously);

        if (it != map.end()) {
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                // 實(shí)例方法
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                // 類方法
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

核心方法是attachCategories,添加分類信息,分為+-方法。

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    // 二維數(shù)組
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    // ?? 如果rew沒(méi)有,初始化rwe 并把ro中的方法、屬性、協(xié)議列表儲(chǔ)存到rwe中
    auto rwe = cls->data()->extAllocIfNeeded();

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle); //排序
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            //倒敘插入
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

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

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

??auto rwe = cls->data()->extAllocIfNeeded(),rwe是在這個(gè)方法初始化的,因?yàn)檫@里要對(duì)roclean memory進(jìn)行處理了,并把ro中的方法、屬性、協(xié)議列表賦值到rwe中,全局搜索cls->data()->extAllocIfNeeded()發(fā)現(xiàn)除了這里還有_class_addProperty、class_addProtocol、addMethods三個(gè)方法進(jìn)行調(diào)用,原理相同就是要改變ro本來(lái)的clean memory的時(shí)候才會(huì)開(kāi)辟。

attachLists方法

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

        if (hasArray()) {
            // many lists -> many lists
            // 舊數(shù)組容量
            uint32_t oldCount = array()->count;
            // 新數(shù)組容量
            uint32_t newCount = oldCount + addedCount;
            // 開(kāi)辟新數(shù)組
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            // 向后移動(dòng)要加入的數(shù)量位
            memmove(array()->lists + addedCount, array()->lists,
                    oldCount * sizeof(array()->lists[0]));
            //memcpy 從什么位置開(kāi)始拷貝什么 放多大
            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;
            // oldlist放最后
            if (oldList) array()->lists[addedCount] = oldList;
            //memcpy 從開(kāi)始位置開(kāi)始放入addedLists,
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

attachLists方法主要是為methods、properties、protocols二維數(shù)組賦值.

  • list = addedLists[0]0-1過(guò)程。
  • 1 list -> many lists1-多過(guò)程,先將舊數(shù)組放到最后位置,然后從位置0插入新數(shù)據(jù)。
  • many lists -> many lists 為多對(duì)多過(guò)程,先將舊數(shù)組后移要添加的數(shù)量位置,然后從位置0插入新數(shù)據(jù)。
map_images流程圖.png

分類加載

1.定義一個(gè)類,兩個(gè)分類,有同名方法

@interface Person : NSObject
-(void)say;
@end
@implementation Person
- (void)say
{
}
//+ (void)load
//{
//    NSLog(@" %s", __func__);
//}

//////////////////////////////////////////

@interface Person (e1)
-(void)addmethod2;
-(void)addmethod1;
@end
-(void)addmethod2
{
    
}
-(void)addmethod1
{
    
}
//
//+ (void)load
//{
//    NSLog(@" %s", __func__);
//}

-(void)say
{
    NSLog(@"e1 %s", __func__);
}
//////////////////////////////////////////
@interface Person (e2)
-(void)aaaaaaa;
@end
@implementation Person (e2)
//+ (void)load
//{
//    NSLog(@" %s", __func__);
////    method_exchangeImplementations(class_getInstanceMethod(self, @selector(say)), class_getInstanceMethod(self, @selector(say2)));
//}
-(void)aaaaaaa
{
    
}
-(void)say
{
    NSLog(@"e2 %s", __func__);
}

main函數(shù)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Person *p =[Person alloc];
        [p say];
    }
    return 0;
}

2.運(yùn)行查看堆棧信息。

  • 1.都不實(shí)現(xiàn)load方法
    分類分析1.png

從打印堆棧順序可知,先執(zhí)行的 main.m中的[p say],開(kāi)始方法的慢速查找,realizeClassWithoutSwift實(shí)現(xiàn)該類,ro->baseMethods()取出的list已經(jīng)包括了所有的方法,來(lái)打印下:

分類分析1方法未排序.png

包括聲明的所有方法,但是是沒(méi)有順序的。往下一步排序:


分類分析1方法已排序.png

已經(jīng)是排序好的方法。

  • 2.主類實(shí)現(xiàn)load方法,分類不實(shí)現(xiàn)
    分類分析2.png

從堆棧調(diào)用信息可知,此流程通過(guò)map_images->_read_images實(shí)現(xiàn)類,在main方法之前調(diào)用,通過(guò)剛才總結(jié)的_read_images的第九步實(shí)現(xiàn)類。

分類分析2.1.png

`ro->baseMethods()`取出的`list`已經(jīng)包括了所有的方法,后續(xù)步驟與第一步相同,只是前面執(zhí)行的時(shí)機(jī)不相同。
  • 3.主類實(shí)現(xiàn)load方法,分類實(shí)現(xiàn)load方法
    分類分析3.png

    分類分析3.1.png

    通過(guò)方法打印可知先調(diào)用的map_images->_read_images實(shí)現(xiàn)類,然后通過(guò)load_images->loadAllCategories-...-->attachCategories添加分類數(shù)據(jù),在此時(shí)創(chuàng)建rwe,把分類方法經(jīng)過(guò)排序后放進(jìn)rwe->methods中,attachLists方法走一對(duì)多過(guò)程。
  • 4.主類不實(shí)現(xiàn)load方法,分類實(shí)現(xiàn)load方法
    分類分析4.png

通過(guò)堆棧打印信息可知,通過(guò)load_images->prepare_load_methods->realizeClassWithoutSwift先實(shí)現(xiàn)本類。

分類分析4.1.png

通過(guò)unattachedCategories.attachToClass->attachCategories添加分類方法,rweattachCategories方法中創(chuàng)建,attachLists添加分類方法走1 list -> many lists過(guò)程。

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • [toc] 類的加載 非懶加載類在運(yùn)行時(shí)處理,懶加載編譯期確定.區(qū)分:方式為load方法,把所有類的加載提前.看代...
    清風(fēng)烈酒2157閱讀 344評(píng)論 0 0
  • 在上一篇文章中探索過(guò)iOS類的加載,也探索了部分分類的加載;那么本篇文章將繼續(xù)接著繼續(xù)探索分類的加載。 在上一篇文...
    大橘豬豬俠閱讀 664評(píng)論 0 1
  • 在 iOS 應(yīng)用程序加載[http://m.itdecent.cn/p/bffb5bdb4f13] 一篇,我...
    賣饃工程師閱讀 1,067評(píng)論 0 5
  • 久違的晴天,家長(zhǎng)會(huì)。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,868評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開(kāi)了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽(tīng)閱讀 10,916評(píng)論 0 11

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