從源碼理解Category、load和initialize

博客連接 從源碼理解Category、load和initialize數組

Category

Objective-C中的Category就是對裝飾模式的一種具體實現。它的主要做用是在不改變原有類的前提下,動態地給這個類添加一些方法。緩存

先看一段代碼,後面的關於Category分析都是基於如下代碼:bash

// CategoryTestModel.h
@interface CategoryTestModel : NSObject

- (void)ins_method1;
+ (void)cls_method1;

@end

@interface CategoryTestModel(NN)

- (void)ins_method2;
- (void)ins_method22;
+ (void)cls_method2;

@end

@interface CategoryTestModel(Nero)

- (void)ins_method3;
+ (void)cls_method3;

@end

// CategoryTestModel.m
@implementation CategoryTestModel

- (void)ins_method1 {
    NSLog(@"%s", __func__);
}

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

@end

@implementation CategoryTestModel(NN)

- (void)ins_method2 {
    NSLog(@"%s", __func__);
}

- (void)ins_method22 {
    NSLog(@"%s", __func__);
}

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

@end

@implementation CategoryTestModel(Nero)

- (void)ins_method3 {
    NSLog(@"%s", __func__);
}

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

@end
複製代碼

上面的代碼聲明瞭一個CategoryTestModel的類以及NNNero兩個分類,接着咱們使用以下命令將這些代碼轉化成C++代碼: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc CategoryTestModel.mapp

Category的底層結構

在C++代碼中,咱們會發現有如下關鍵代碼:iphone

// NN分類
static struct _category_t _OBJC_$_CATEGORY_CategoryTestModel_$_NN __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"CategoryTestModel",
	0, // &OBJC_CLASS_$_CategoryTestModel,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryTestModel_$_NN,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CategoryTestModel_$_NN,
	0,
	0,
};

// Nero分類
static struct _category_t _OBJC_$_CATEGORY_CategoryTestModel_$_Nero __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"CategoryTestModel",
	0, // &OBJC_CLASS_$_CategoryTestModel,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryTestModel_$_Nero,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CategoryTestModel_$_Nero,
	0,
	0,
};
複製代碼

從源碼中能夠知道,分類轉化成了_category_t類型的結構體,而且有幾個分類,就會對應生成幾個這樣的結構體。這裏有一點要說明一下,在objc的代碼中也能夠找到關於分類的結構體介紹,結構體名叫category_t,併成員變量稍微有點差別,可是不影響對底層實現的學習。函數

_category_t的定義以下:學習

struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};
複製代碼

從代碼中咱們能夠看出_category_t結構體中存放着類名,主類名,對象方法列表,類方法列表,協議列表,以及屬性列表。測試

在咱們聲明的分類中,只有對象方法和類方法,因此咱們看一下_method_list_t_method_list_t的定義以下:ui

// 對象方法存儲
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CategoryTestModel_$_NN __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"ins_method2", "v16@0:8", (void *)_I_CategoryTestModel_NN_ins_method2},
	{(struct objc_selector *)"ins_method22", "v16@0:8", (void *)_I_CategoryTestModel_NN_ins_method22}}
};

// 類方法存儲
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_CategoryTestModel_$_NN __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"cls_method2", "v16@0:8", (void *)_C_CategoryTestModel_NN_cls_method2}}
};
複製代碼

經過上面的一系列源碼,咱們知道了以下幾點:this

  • Category的底層結構是一個_category_t的結構體,咱們在分類中聲明的方法、屬性等都會存在對應的字段中;
  • 有多少個分類就會有多少的_category_t結構體。

到這裏就難免會出現一個疑問: 在咱們將代碼轉化成C++代碼後,一個類有其主類_class_t還有N個_category_t的分類。主類方法存在主類的結構中,分類方法存在分類的結構體中。咱們打印其方法列表的時候,分類方法也會打印出來,另外咱們在平常開發中,有時也會使用分類去覆蓋掉主類實現的方法,那這又是爲何?上面所講的只是編譯期的過程,Category的實現還依賴於Runtime。

這裏插一個題外話,在平常開發的時候,在一些比較麻煩的時候咱們可能會使用方法交叉,咱們也知道方法交叉是比較危險的手段,仍是少用爲妙。而上面提到的使用分類去覆蓋掉主類實現的方法也是能減小方法交叉的使用。

Category在運行時中

這裏使用objc4-750.1的源代碼,你能夠經過這裏下載。在objc-runtime-new.m中找到一個_read_images的函數,因爲代碼很長,這裏只貼出關於分類的實現:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    // 這裏只顯示category的實現
        // Discover categories. 
    for (EACH_HEADER) {
        // 二維數組存在分類結構體變量
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        // 判斷是否有類屬性
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            // 獲取分類的主類
            Class cls = remapClass(cat->cls);
            // 主類不存在進行下一次循環
            if (!cls) {
                // 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) 
            {
                // 給主類添加獨立的分類
                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);
                }
            }
        }
    }

    ts.log("IMAGE TIMES: discover categories");  
}
複製代碼
  1. 經過_getObjc2CategoryList函數獲取到分類列表;
  2. 遍歷分類列表獲取其中的方法,協議,屬性等;
  3. 調用remethodizeClass(cls)函數對類對象(分類的主類)從新方法化;
  4. 調用remethodizeClass(cls->ISA());對類對象的元類從新方法化;

能夠看到最終都調用了remethodizeClass函數,接着咱們看一下remethodizeClass的實現:

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        // 附加分類
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}
複製代碼

remethodizeClass的實現主要依賴於attachCategories,從函數名咱們能夠知道這個函數,就是用來給類或者元類添加分類中存放的數據。attachCategories的實現以下:

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));
    // 屬性列表
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    // 協議列表
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // 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--) {
        auto& entry = cats->list[i];

        // 根據是不是元類返回分類結構體中的類方法列表或者對象方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        // 根據是不是元類返回分類結構體中的類屬性列表或者屬性列表
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
     
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    // rw表明類對象,裏面有一個方法列表和屬性列表的字段
    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    // 將上面合併後的分類方法合併到類對象的方法列表中,並釋放mlists
    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);
    free(protolists);
}
複製代碼

從上面的代碼能夠看到,方法、屬性、協議的合併都使用了attachLists方法,其實現以下:

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)));
        array()->count = newCount;
        // array()->lists是原來的數組
        // memmove的做用就是內存移動,將原來的數組向後拖動oldCount * sizeof(array()->lists[0])個位置
        memmove(array()->lists + addedCount, array()->lists,
                oldCount * sizeof(array()->lists[0]));
        // memcpy的做用是內存拷貝,將新數組拷貝到前面空出的位置
        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]));
    }
}
複製代碼

關於上述關於合併的實現分紅了三種狀況,可是思想都接近,簡單的說就是數組合並。分析這段代碼是爲了解釋爲何咱們能夠在分類中重寫方法來覆蓋主類的實現。

這裏咱們就以分類方法在many lists -> many lists的狀況下來分析一下這個流程。

  1. 首頁分配一塊內存用來數組擴容,擴容後的長度就是類對象的方法列表的長度加上分類方法列表的長度;
  2. 調用memmove,將原來的方法列表向後拖動oldCount * sizeof(array()->lists[0])個位置;
  3. 調用memcpy,將分類方法列表拷貝在前面空出的位置。

用一張圖表示下上面的狀況:

category_attachList

Categoty總結

這裏再總結一下Categoty的一些知識點:

  1. Category在編譯時期會轉化成_category_t的結構體,咱們在分類中聲明的方法、屬性等都會存在對應的字段中;
  2. 在程序運行時,runtime會將Category的數據合併到類對象或者元類對象中;
  3. 分類方法列表在主類方法列表的前面,因此分類的同名方法會被優先被調用或者分類中重寫方法能覆蓋主類的實現
  4. 分類列表中越後面的分類即越後面參與編譯的分類,其方法列表中的方法會越優先被調用。
  5. 分類中的屬性也是在運行時的時候完成的,這要區別於Extension。Extension能夠理解爲將.h文件中的共有屬性聲明在.m中變成私有的,那這些屬性也是在編譯期完成的。因此Category和Extension的區別就是Extension在編譯時期,它的數據就包含在類信息中,Category中的數據是在運行時才合併到類中

可是有兩個方法比較特殊,分別是loadinitialize,它們的執行結果和通常方法有區別,因此單獨拿出來分析。

load

首先看一段代碼:

// FatherA
@implementation FatherA

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

@end

@implementation FatherA(NN)

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

@end

@implementation FatherA(Nero)

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

@end

// SonA
@implementation SonA

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

@end

@implementation SonA(NN)

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

@end

@implementation SonA(Nero)

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

@end

// FatherB
@implementation FatherB

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

// SonB
@implementation SonB

+ (void)load {
    NSLog(@"%s", __func__);
}
複製代碼

編譯順序以下:

load編譯順序

執行結果以下:

load執行順序

FatherA爲例,咱們知道load方法會在runtime加載類、分類的時候進行調用,根據上面提到的分類會覆蓋主類的同名方法(這裏並非真的覆蓋,而是優先於主類先調用),load函數應該只是被調用一次,可是經過上面的代碼咱們看到load方法被調用了三次,這是爲何呢?

load方法的實如今objc-runtime-new.m中找到一個load_images的函數,其實現以下:

// load_images
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
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
複製代碼

上面代碼的實現主要依賴於prepare_load_methodscall_load_methodsprepare_load_methods用來查找load方法並肯定其調用順序,call_load_methods用來調用load方法。

prepare_load_methods

咱們先看一下prepare_load_methods的實現:

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

    runtimeLock.assertLocked();

    // 獲取全部的類,classlist中類的順序按照誰先編譯,誰就在數組的前面
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 規劃類的加載,即肯定+load方法的調用順序
        schedule_class_load(remapClass(classlist[i]));
    }

    // 獲取全部的分類
    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());
        // 將實現了+load方法的分類添加到loadable_categories數組的最後面
        add_category_to_loadable_list(cat);
    }
}
複製代碼

prepare_load_methods的實現能夠分爲兩個步驟:

  1. 獲取全部的類,調用schedule_class_load
  2. 獲取全部的分類,調用add_category_to_loadable_listadd_category_to_loadable_list 將實現了+load方法的分類添加到loadable_categories數組的最後面,loadable_categories用來在call_load_methods進行分類+load方法的調用。

上面代碼中,關於classlistcategorylist這兩個數組中的元素的順序是根據類或者分類被編譯的順序分別進入對應數組中的。

類load方法的調用順序

肯定類load方法的調用順序,依賴於schedule_class_load,其實現以下:

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

    // Ensure superclass-first ordering
    // 沿着cls的父類一直往上查找知道NSObject
    schedule_class_load(cls->superclass);

    // 將實現了+load的cls添加到loadable_classes的最後面
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
複製代碼

schedule_class_load是一個遞歸函數,它會沿着cls的父類一直往上查找知道NSObject,接着調用add_class_to_loadable_list將實現了+load方法的cls添加到loadable_classes的最後面。這也就解釋了爲何父類的load方法爲何會優先於子類的load方法被調用。

接着再看一下add_class_to_loadable_list的實現:

// 這個結構體中的method專門存放類中的+load方法
struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method if (PrintLoading) { _objc_inform("LOAD: class '%s' scheduled for +load", cls->nameForLogging()); } 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++; } 複製代碼

這個代碼的做用就是經過傳入的cls生成一個對應的loadable_class結構體,並將這個結構體加在loadable_classes的最後面,loadable_class這個結構體中的method專門存放類中的+load方法。

分類load方法的調用順序

分類load方法的調用順序就是遍歷獲取到的分類列表,經過add_category_to_loadable_list 將實現了+load方法的分類添加到loadable_categories數組的最後面。add_category_to_loadable_list的實現以下:

// 這個結構體中的method專門存放分類中的+load方法
struct loadable_category {
    Category cat;  // may be nil
    IMP method;
};

void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method if (!method) return; if (PrintLoading) { _objc_inform("LOAD: category '%s(%s)' scheduled for +load", _category_getClassName(cat), _category_getName(cat)); } if (loadable_categories_used == loadable_categories_allocated) { loadable_categories_allocated = loadable_categories_allocated*2 + 16; loadable_categories = (struct loadable_category *) realloc(loadable_categories, loadable_categories_allocated * sizeof(struct loadable_category)); } loadable_categories[loadable_categories_used].cat = cat; loadable_categories[loadable_categories_used].method = method; loadable_categories_used++; } 複製代碼

add_category_to_loadable_list函數和add_class_to_loadable_list函數的實現邏輯很類似,這個就不作說明了。

這裏總結一下load方法的調用順序:

  • 對於重寫了+load方法的類來講,其load方法的調用順序是先編譯的類的父類 > 先編譯的類 > 後編譯的類的父類 > 後編譯的類
  • 對於重寫了+load方法的類來講,其load方法的調用順序是誰先被編譯,誰就優先被調用。

當肯定好了load的調用順序了之後,就須要調用load方法。

call_load_methods

接着咱們看一下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.
    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) { // 調用類的load方法 call_class_loads(); } // 2. Call category +loads ONCE // 調用分類的load方法 more_categories = call_category_loads(); // 3. Run more +loads if there are classes OR more untried categories } while (loadable_classes_used > 0 || more_categories); objc_autoreleasePoolPop(pool); loading = NO; } 複製代碼

call_load_methods函數也是分爲兩個步驟:

  1. 使用call_class_loads調用類的load方法;
  2. 使用call_category_loads調用分類的load方法。

call_class_loads

call_class_loads的實現以下:

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.
    // 遍歷loadable_classes列表調用+load方法,數組中越前面的元素越先被調用
    // 根據schedule_class_load的遞歸查找還能夠知道,父類的load方法要先於子類進行調用
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        // 取出類中的load方法
        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);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
複製代碼

call_class_loads函數經過遍歷遍歷loadable_classes列表調用+load方法,數組中越前面的元素越先被調用。經過load_method_t能夠獲取類的load方法,load_method_t的定義爲:

typedef void(*load_method_t)(id, SEL);
複製代碼

load_method_t是指向函數的指針,至關於返回的是一個函數地址。獲取到函數指針後,使用(*load_method)(cls, SEL_load);調用load方法。因此說,load方法的調用並非使用消息發送機制,而是直接使用函數指針調用

call_category_loads

call_category_loads用來調用分類的load方法,其實現以下:

static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    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++) {
        Category cat = cats[i].cat;
        // 取出分類中的load方法,返回值是一個函數地址
        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);
            cats[i].cat = nil;
        }
    }

    // Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }

    // 如下代碼是一些釋放代碼,能夠忽略
    // Destroy the new list.
    if (loadable_categories) free(loadable_categories);

    // Reattach the (now augmented) detached list. 
    // But if there's nothing left to load, destroy the list. if (used) { loadable_categories = cats; loadable_categories_used = used; loadable_categories_allocated = allocated; } else { if (cats) free(cats); loadable_categories = nil; loadable_categories_used = 0; loadable_categories_allocated = 0; } if (PrintLoading) { if (loadable_categories_used != 0) { _objc_inform("LOAD: %d categories still waiting for +load\n", loadable_categories_used); } } return new_categories_added; } 複製代碼

load總結

這裏再總結一下load方法的一些知識點:

  1. load方法會在runtime加載類、分類的時候進行調用,不須要引入,只要被加載進內存就會被調用;
  2. 每個類、分類的load,在程序運行過程當中只會被調用一次;
  3. 先調用類的load方法,調用順序:先編譯的類的父類 > 先編譯的類 > 後編譯的類的父類 > 後編譯的類;
  4. 再調用分類的load方法,調用順序:誰先被編譯,誰就優先被調用;
  5. load方法的調用本質是使用函數地址直接進行調用,而不是使用objc_msgSend的方式。

initialize

initialize的測試代碼以下:

// FatherA
@implementation FatherA

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

@end

@implementation FatherA(NN)

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

@end

@implementation FatherA(Nero)

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

@end

// SonA
@implementation SonA

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

@end

@implementation SonA(NN)

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

@end

@implementation SonA(Nero)

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

@end

// FatherB
@implementation FatherB

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

// SonB
@implementation SonB

+ (void)initialize {
    NSLog(@"%s", __func__);
}
複製代碼

咱們知道initialize方法會在類第一次接收到消息的時候被調用,因此咱們使用以下代碼進行測試:

// 測試代碼
[SonA alloc];
[SonA alloc];
[SonA alloc];
    
[FatherB alloc];
複製代碼

執行結果以下:

initialize執行順序

經過上面的代碼能夠知道:

  1. initialize方法會在類第一次接收到消息的時候被調用,因此類的initialize可能永遠不會被調用;
  2. 父類的initialize會優先於子類被調用;
  3. 分類的initialize會覆蓋掉主類的initialize(這也符合以前分類的方法調用順序);

initialize的實現

initialize方法的實如今objc-runtime-new.m中找到一個lookUpImpOrForward的函數,其中有以下關鍵代碼:

if (initialize  &&  !cls->isInitialized()) {
    runtimeLock.unlock();
    _class_initialize (_class_getNonMetaClass(cls, inst));
    runtimeLock.lock();
    // If sel == initialize, _class_initialize will send +initialize and
    // then the messenger will send +initialize again after this
    // procedure finishes. Of course, if this is not being called
    // from the messenger then it won't happen. 2778172 } 複製代碼

在方法調用過程當中,若是類沒有被初始化的時候,會調用_class_initialize對類進行初始化,其中有兩塊關鍵代碼:

/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void _class_initialize(Class cls)
{
    ...
    
    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        // 沿着cls的父類一直往上查找知道NSObject
        _class_initialize(supercls);
    }
    
    ...
            //  使用objc_msgSend調用initialize方法
            callInitialize(cls);
    ...
}
複製代碼

_class_initialize方法會進行遞歸調用,由此能夠確保父類優先於子類初始化。接着調用callInitialize函數,其實現以下:

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

因此initialize的調用方式是objc_msgSend,它和普通方法同樣是由Runtime經過發消息的形式。

initialize總結

這裏再總結一下initialize方法的一些知識點:

  1. initialize方法會在類第一次接收到消息的時候被調用,因此類的initialize可能永遠不會被調用;
  2. initialize調用順序: 父類的initialize會優先於子類被調用,且分類的initialize會覆蓋主類的initialize,那麼就會對這個類中的實現形成覆蓋;
  3. 若是子類沒有實現initialize方法,那麼繼承自父類的實現會被調用即父類的initialize可能被調用屢次;
  4. 若是咱們想確保本身的initialize方法只執行一次,避免屢次執行可能帶來的反作用時,咱們可使用下面的代碼來實現:
+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}
複製代碼

最後總結一下load和initialize區別

/ load initialize
調用時機 加載到runtime時 收到第一條消息時,可能永遠不調用
調用本質 函數地址調用 objc_msgSend發送消息
調用順序 父類 > 類 > 分類 父類 > 類
調用次數 一次 一次或者屢次
分類的中實現 類和分類都執行 "覆蓋"主類中的實現
相關文章
相關標籤/搜索