iOS底層原理 - Category實現(xiàn)原理(二)

通過探索Category底層原理回答以下問題

  1. Category是否可以添加方法、屬性、成員變量?Category是否可以遵守Protocol?
  2. Category的本質(zhì)是什么,在底層是怎么存儲的?
  3. Category的實現(xiàn)原理是什么,Catagory中的方法是如何調(diào)用到的?
  4. Category中是否有Load方法,load方法是什么時候調(diào)用的?
  5. load、initialize的區(qū)別

Category實現(xiàn)原理(一)中我們通過窺探Category底層結(jié)構(gòu)回答了問題1、2,下面我們繼續(xù)探究。

Category中的方法調(diào)用順序 - 表象

創(chuàng)建父類MGCPerson,為其添加分類MGCPerson+SportMGCPerson+Eat,創(chuàng)建子類MGCStudent,并添加分配MGCStudent+Study,然后分別添加方法- (void)life+ (void)life

@implementation MGCPerson
- (void)life {
    NSLog(@"MGCPerson : - (void)life");
}

+ (void)life {
    NSLog(@"MGCPerson : + (void)life");
}
@end

@implementation MGCPerson (Sport)
- (void)life {
    NSLog(@"MGCPerson (Sport) : - (void)life");
}

+ (void)life {
    NSLog(@"MGCPerson (Sport) : + (void)life");
}
@end

@implementation MGCPerson (Eat)
- (void)life {
    NSLog(@"MGCPerson (Eat) : - (void)life");
}

+ (void)life {
    NSLog(@"MGCPerson (Eat) : + (void)life");
}
@end
@implementation MGCStudent
- (void)life {
    NSLog(@"MGCStudent : - (void)life");
}

+ (void)life {
    NSLog(@"MGCStudent : + (void)life");
}
@end

@implementation MGCStudent (Study)
- (void)life {
    NSLog(@"MGCStudent (Study) : - (void)life");
}

+ (void)life {
    NSLog(@"MGCStudent (Study) : + (void)life");
}
@end

在main函數(shù)中創(chuàng)建MGCStudent類,調(diào)用life方法,觀察打印

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MGCStudent *student = [[MGCStudent alloc] init];
        [student life];
        [MGCStudent life];
        MGCPerson *person = [[MGCPerson alloc] init];
        [person life];
        [MGCPerson life];
    }
    return 0;
}

// 打印結(jié)果
MGCStudent (Study) : - (void)life
MGCStudent (Study) : + (void)life
MGCPerson (Sport) : - (void)life
MGCPerson (Sport) : + (void)life

觀察打印結(jié)果我們可以得出一個結(jié)論:Category中的方法會”覆蓋“原類中的方法;仔細(xì)觀察我們發(fā)現(xiàn)MGCPerson相關(guān)的調(diào)用打印全部來自MGCPerson (Sport)中的方法,但我們明明在MGCPerson的兩個分類MGCPerson (Sport)、MGCPerson (Eat)中都實現(xiàn)了life方法,為什么優(yōu)先調(diào)用了MGCPerson (Sport)中的life方法?我們猜測這和文件的編譯順序有關(guān),通過在Build Phase -> Compile source中調(diào)整文件順序觀察打印結(jié)果,我們發(fā)現(xiàn)后編譯的分類文件優(yōu)先調(diào)用。
總結(jié)起來就是:

  • 分類中的方法實現(xiàn)會"覆蓋"原類中的方法
  • 后編譯的分類文件優(yōu)先級更高

繼續(xù)研究為什么調(diào)用順序是這樣

Category方法調(diào)用順序 - 本質(zhì)

為什么去runtime中找對應(yīng)源碼?

  • OC中的方法調(diào)用簡單的說就是通過實例對象(或類對象)的isa指針和類對象(或元類對象)的superClass指針去類(或元類)對象中查找方法。
  • 通上篇文章的探究我們知道,Category中的方法、屬性等編譯后是存儲在category_t結(jié)構(gòu)體中的,也就是說編譯后分類中的方法并沒有合并到類(或元類)中,我們是無法在類對象(或元類對象)中找到Category中的方法的。
  • 但是最終調(diào)用的時候我們卻可以通過isa和superClass指針找到這些方法。所以我們有理由猜測runtime幫我們做了方法合并

為什么從void _objc_init(void)開始查找?
簡單的說下APP安裝到運(yùn)行的過程:

  1. 從appStore下載app,簽名認(rèn)證通過后裝載到手機(jī)磁盤中
  2. 點擊APP,系統(tǒng)內(nèi)核部署好APP(進(jìn)程)運(yùn)行的初始環(huán)境,找到對應(yīng)APP的可執(zhí)行文件(Mach-O文件)
    • Mach-O是蘋果系統(tǒng)的一種文件格式,app的可執(zhí)行文件、動態(tài)庫、靜態(tài)庫最終都是這種文件格式
  3. 根據(jù)Mach-O文件找到dyld的路徑,內(nèi)核加載dyld,dyld從系統(tǒng)留下的原始調(diào)用棧引導(dǎo)和啟動自己
    • dyld(dynamic link editor) 動態(tài)鏈接器,用于鏈接動態(tài)庫、資源、app可執(zhí)行文件格式的Mach-O文件
    • dyld 本身也是一種Mach-O文件
  4. dyld將APP可執(zhí)行文件(Mach-O文件)加載到內(nèi)存中,跟據(jù)APP可執(zhí)行文件中的 LoadCommands中的信息去系統(tǒng)共享緩存區(qū)(dyld shared cache)將引用的動態(tài)庫(Mach-O文件)遞歸加載進(jìn)內(nèi)存
  5. 鏈接包括可執(zhí)行文件在內(nèi)APP所需的所有的Mach-O文件
  6. 初始化除可執(zhí)行文件外的所有Mach-O文件
  7. 初始化APP可執(zhí)行文件,調(diào)用app中的main函數(shù),到這一步就進(jìn)入我們熟悉的APP入口main函數(shù)了

_objc_init就是runtime的初始化函數(shù),也就是在上述第6步時調(diào)用的。
在runtime源碼中搜索_objc_init

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

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

可以看到在一系列的初始化后,調(diào)用了_dyld_objc_notify_register并且傳遞了三個函數(shù),去dyld源碼中搜索函數(shù)_dyld_objc_notify_register的定義

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

根據(jù)注釋在_dyld_objc_notify_register中會調(diào)用傳入的mapped函數(shù),并傳入已映射進(jìn)內(nèi)存的objc image(objc對應(yīng)的模塊)

objc image中存儲著我們定義的類及分類信息,接下來去runtime源碼中查看map_images函數(shù)

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

這個函數(shù)實際返回的是map_images_nolock函數(shù)的地址,繼續(xù)查看map_images_nolock函數(shù),map_images_nolock函數(shù)中查找了所有包含Objective-Cimage ,然后調(diào)用_read_images處理這些image,繼續(xù)查看_read_images函數(shù)
_read_images函數(shù)中找到處理分類的代碼塊

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    // Discover categories. 
    for (EACH_HEADER) { // 遍歷image
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count); // 獲取當(dāng)前image中的分類列表
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) { // 遍歷分類
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) { // 分類對應(yīng)的原類已經(jīng)釋放了,清空這個分類,打印信息信息,繼續(xù)下次循環(huán)
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties)  // 分類有方法、協(xié)議或?qū)傩?            {
                addUnattachedCategoryForClass(cat, cls, hi); // 記錄這個分類到一個映射表中
                if (cls->isRealized()) { // 這個類的所有分類已找到
                    remethodizeClass(cls); // 重新整合類中信息
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

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

_read_images函數(shù)讀取完image中的Category信息后存放起來,然后調(diào)用remethodizeClass函數(shù)。
remethodizeClass中又調(diào)用了附加分類的方法attachCategories,重點來了,下邊我們看下attachCategories函數(shù)。注釋也很重要,注意下述代碼中的注釋

static void  attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));  //malloc一個二維數(shù)組存放所有分類的方法列表
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists)); //malloc一個二維數(shù)組存放所有分類的屬性列表
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists)); //malloc一個二維數(shù)組存放所有分類的協(xié)議列表

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {  // 注意這里,是i--也就是說是倒序處理分類的
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist; // 按編譯倒序(i--)將各分類的方法列表(mlist) 按正序(mcount++) 放進(jìn) 二維方法列表(mlists)中
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;// 按編譯倒序(i--)將各分類的屬性列表(proplist) 按正序(propcount++) 放進(jìn) 二維屬性列表(proplists)中
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;// 按編譯倒序(i--)將各分類的協(xié)議列表(protolist) 按正序(protocount++) 放進(jìn) 二維協(xié)議列表(protolists)中
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount); // 將某個類的所有分類的方法列表附加到類的方法列表中
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);// 將某個類的所有分類的屬性列表附加到類的方法列表中
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);// 將某個類的所有分類的協(xié)議列表附加到類的方法列表中
    free(protolists);
}

attachCategories按編譯倒序將各分類中的方法、協(xié)議、屬性列表分別整合成一個二維數(shù)組后,又各自調(diào)用了attachLists方法將整合后的分類信息附加到原類(target class)的cls->data->rw中

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

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); // 數(shù)組擴(kuò)容
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0])); // 從array()->lists位置開始移動oldCount * sizeof(array()->lists[0])字節(jié)到array()->lists + addedCount位置
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0])); // addedLists位置來時,拷貝addedCount * sizeof(array()->lists[0])字節(jié)到array()->lists,即將拷貝到array()的前addedCount位置上
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

分析上述代碼,可以看到是先將原類中的方法后移,然后再將分類中的方法加入到原類中的,到此我們可以得出以下結(jié)論

  • 分類中的方法優(yōu)先級高于原類中的方法
  • 后編譯的分類優(yōu)先級高于先編譯的分類
  • 我們常說的分類方法覆蓋原類方法并不是真正的覆蓋,只是objc_msgSend在分類中找到方法實現(xiàn)后不再繼續(xù)查找

Category中的+load方法

同樣的我們先分別在類及其分類中實現(xiàn)+load+ (void)initialize方法,供后續(xù)分析使用

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

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

@implementation MGCPerson (Sport)
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

@implementation MGCPerson (Eat)
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end
@interface MGCStudent : MGCPerson
@end
@implementation MGCStudent
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

@implementation MGCStudent (Study)
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

@implementation MGCStudent (Holiday)
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end
@interface MGCDog : NSObject
@end
@implementation MGCDog
+ (void)load {
    NSLog(@"%s", __func__);
}

+ (void)initialize {
    NSLog(@"%s", __func__);
}
@end

添加好代碼后,不做任何調(diào)用和對象創(chuàng)建,直接運(yùn)行程序,打印如下

+[MGCDog load]
+[MGCPerson load]
+[MGCStudent load]
+[MGCPerson(Eat) load]
+[MGCPerson(Sport) load]
+[MGCStudent(Holiday) load]
+[MGCStudent(Study) load]

可以發(fā)現(xiàn)未做任何調(diào)用和對象創(chuàng)建的情況下,也會執(zhí)行+ (void)load方法。
嘗試在xcode->targets->build phases->compile sources中調(diào)整文件編譯順序,發(fā)現(xiàn)

  • 類中的load方法始終優(yōu)先于分類調(diào)用,且不受編譯順序影響
  • 父類中的load方法優(yōu)先于子類調(diào)用,且不受編譯順序影響
  • 無繼承關(guān)系的類,先編譯的先調(diào)用
  • 分類中的load方法先編譯的先調(diào)用,且與其對應(yīng)原類的繼承關(guān)系無關(guān)

既然我們沒有調(diào)用,我們猜測是runtime啟動時調(diào)用了load方法,繼續(xù)查看runtime的入口函數(shù)_objc_init

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

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

從前邊對void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped);的分析我們知道第二個參數(shù)load_images函數(shù)中調(diào)用了+load方法,從這里也可以看出load_images中的load不是加載的意思,是load方法的意思

void load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh); // 準(zhǔn)備工作
    }

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

load_images中先調(diào)用了prepare_load_methods再進(jìn)一步調(diào)用了void call_load_methods(void)
先看下prepare_load_methods做了什么處理,注釋很重要,不要忽略它

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

    runtimeLock.assertWriting();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count); // 獲取所有類
    for (i = 0; i < count; i++) { // 正序遍歷所有類(編譯順序)
        schedule_class_load(remapClass(classlist[i])); // 整理類的load方法
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count); // 拿到所有的分類
    for (i = 0; i < count; i++) {  // 正序遍歷所有分類(編譯順序)
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat); // 正序添加到表中
    }
}

分析上述代碼可知:類中的load方法優(yōu)先于Category中的load方法被規(guī)劃入表中;且類及分類各自按編譯正序規(guī)劃load方法到表中。
繼續(xù)查看schedule_class_load方法,注釋依然很重要,不要忽略它

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return; // 如果已經(jīng)處理過此類的load方法,則不再處理,所以每個load方法只會調(diào)用一次

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass); // 遞歸調(diào)用schedule_class_load方法,并傳入父類

    add_class_to_loadable_list(cls); // 將類中的load方法加入表中
    cls->setInfo(RW_LOADED);  // 置為已處理
}

分析上述代碼可知:父類中的load方法優(yōu)先于子類中的load方法加入表中。
到此準(zhǔn)備工作就做完了,接下來看下調(diào)用void call_load_methods(void)

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

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) { 
            call_class_loads(); // 調(diào)用所有類的load方法,地都用完成后loadable_classes_used置為0
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads(); // 調(diào)用所有分類的load方法

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

分析上述代碼可知:調(diào)用load方法時優(yōu)先調(diào)用類中的load方法,然后再調(diào)用分類中的load方法

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;  // 從表中取出所有類
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) { // 按取出結(jié)果正序調(diào)用
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

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

分析上述代碼可知:類中的load方法按表中正序調(diào)用,結(jié)合前邊load方法的入表順序可知,先編譯的類先調(diào)用,且父類優(yōu)先于子類調(diào)用。
查看call_category_loads(void)方法

    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) { // 按表中正序調(diào)用
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load); // 調(diào)用load方法
            cats[i].cat = nil;
        }
    }

分析上述代碼可知:分類中的load方法按表中正序調(diào)用,結(jié)合前邊入表順序可知,先編譯的分類先調(diào)用。
綜合以上分析,已經(jīng)驗證了本小結(jié)開始時的現(xiàn)象分析結(jié)論,這里不再贅述

Category中的+ (void)initialize方法

同樣先看現(xiàn)象,代碼和上一小結(jié)(Category中的+load方法)中一樣,這里不再列出
上一小結(jié)中我們在不添加任何方法調(diào)用、對象創(chuàng)建的情況下直接運(yùn)行程序,發(fā)現(xiàn)調(diào)用了+load方法,但是并沒有調(diào)用+ (void)initialize
嘗試添加如下代碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [MGCStudent class];
        [[MGCDog alloc] init];
        [[MGCDog alloc] init];
        [[MGCDog alloc] init];
        [[MGCDog alloc] init];
    }
    return 0;
}

打印結(jié)果

+[MGCPerson(Eat) initialize]
+[MGCStudent(Study) initialize]
+[MGCDog initialize]

可以看到在我們沒有調(diào)用+ (void)initialize的情況下,+ (void)initialize方法依然被調(diào)用了,且無論調(diào)用的是類類方法還是實例方法,+ (void)initialize都會被調(diào)用并且只會調(diào)用一次。
因此我們猜測是類第一次接收到消息時調(diào)用了+ (void)initialize方法。即第一次接收objc_msgSend消息時調(diào)用了+ (void)initialize方法。
嘗試去runtime中查找objc_msgSend方法,這個方法的源碼是匯編的。
最終調(diào)用到了void _class_initialize(Class cls)方法

void _class_initialize(Class cls) {
    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls); // 遞歸調(diào)用_class_initialize方法,并傳入父類指針
    }
    
    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) { // 如果類沒有調(diào)用Initialize方法,則準(zhǔn)備調(diào)用
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }

    if (reallyInitialize) {
          callInitialize(cls); // 調(diào)用當(dāng)前類的callInitialize方法
    }
}

分析上述代碼可知:在調(diào)用自己的callInitialize之前會先調(diào)用父類的callInitialize

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

分析上述代碼可知:Initialize最終是通過objc_msgSend調(diào)用的,既然最終是通過objc_msgSend調(diào)用的,那就遵從objc_msgSend方法調(diào)用的原理。即分類方法會"覆蓋"原類的中的方法。
綜合以上可知:

  • 父類中的Initialize優(yōu)先于子類中的Initialize方法調(diào)用
  • 分類中的Initialize優(yōu)先于類中的Initialize方法調(diào)用
    注意:如果子類及子類分類中都沒有實現(xiàn)Initialize方法,根據(jù)objc_msgSend方法查找順序,最終會調(diào)用父類的Initialize方法。也就是說上一小節(jié)中定義的MGCStudent對象及其分類MGCStudent (Study)MGCStudent (Holiday)如果都沒實現(xiàn)Initialize,調(diào)用[MGCStudent class]會調(diào)用兩次Initialize方法,一次是遞歸調(diào)用時父類(MGCPerson)調(diào)用的,一次是自己調(diào)用時objc_msgSend根據(jù)方法查找順序自己(MGCStudent)調(diào)用的
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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