Category底層原理

一 Category基本使用
二 Category的底層結構
三 Category的加載處理流程
四 Category的load方法講解
五 Category的initialize方法講解

Category也叫分類或類別,是OC提供的一種擴展類的方式。不管是自定義的類還是系統的類,我們都可以通過Category給原有類擴展方法(實例方法和類方法都可以),而且擴展的方法和原有的方法的調用方式是一模一樣的。比如我項目中經常需要統計一個字符串中字母的個數,但是系統沒有提供這個方法,那我們就可以用Category給NSString類擴展一個方法,然后只需引入Category的頭文件就可以和調用系統方法一樣來調用擴展的方法

一 Category基本使用

下面我們來看一下Category的基本使用示例:

#import <Foundation/Foundation.h>
@interface People : NSObject

- (void)talk;
@end

#import "People.h"

@implementation People

- (void)talk{
   
   NSLog(@"%s:can I speak?",__func__);
}
@end

#import "People.h"

@interface People (Speak)

-(void)speak;

@end

#import "People+Speak.h"

@implementation People (Speak)

-(void)speak{
    
    NSLog(@"%s: I can speak",__func__);
}

@end

#import "People.h"

@interface People (Eat)

-(void)eat;

@end


#import "People+Eat.h"

@implementation People (Eat)

-(void)eat{
    
    NSLog(@"%s: I can eat food",__func__);
}

@end
#import <Foundation/Foundation.h>
#import "People.h"
#import "People+Speak.h"
#import "People+Eat.h"
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *people = [[People alloc] init];
        [people talk];
        [people speak];
        [people eat];
       
    }
    return 0;
}
output:
2019-02-07 13:52:08.128350+0800 thread[1362:24336] -[People talk]:can I speak?
2019-02-07 13:52:08.128735+0800 thread[1362:24336] -[People(Speak) speak]: I can speak
2019-02-07 13:52:08.128778+0800 thread[1362:24336] -[People(Eat) eat]: I can eat food

分類的使用是非常簡單的,為什么給一個類添加的分類而且分類的方法和原有的方法的調用方式是一模一樣的都能通過這個類的對象進行調用呢,我們一起來探究一下。

二 Category的底層結構

我們把People+Speak.m底層轉換成C++來看一下
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc People+Speak.m

#ifndef _REWRITER_typedef_People
#define _REWRITER_typedef_People
typedef struct objc_object People;
typedef struct {} _objc_exc_People;
#endif

struct People_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

static void _I_People_Speak_speak(People * self, SEL _cmd) {

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_People_Speak_8c87eb_mi_0,__func__);
}

// @end

struct _prop_t {
    const char *name;
    const char *attributes;
};

struct _protocol_t;

struct _objc_method {
    struct objc_selector * _cmd;
    const char *method_type;
    void  *_imp;
};

struct _protocol_t {
    void * isa;  // NULL
    const char *protocol_name;
    const struct _protocol_list_t * protocol_list; // super protocols
    const struct method_list_t *instance_methods;
    const struct method_list_t *class_methods;
    const struct method_list_t *optionalInstanceMethods;
    const struct method_list_t *optionalClassMethods;
    const struct _prop_list_t * properties;
    const unsigned int size;  // sizeof(struct _protocol_t)
    const unsigned int flags;  // = 0
    const char ** extendedMethodTypes;
};

struct _ivar_t {
    unsigned long int *offset;  // pointer to ivar offset location
    const char *name;
    const char *type;
    unsigned int alignment;
    unsigned int  size;
};

struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    const unsigned char *ivarLayout;
    const char *name;
    const struct _method_list_t *baseMethods;
    const struct _objc_protocol_list *baseProtocols;
    const struct _ivar_list_t *ivars;
    const unsigned char *weakIvarLayout;
    const struct _prop_list_t *properties;
};

struct _class_t {
    struct _class_t *isa;
    struct _class_t *superclass;
    void *cache;
    void *vtable;
    struct _class_ro_t *ro;
};

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;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"speak", "v16@0:8", (void *)_I_People_Speak_speak}}
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_People;
//根據分類的定義,給_category_t賦值
static struct _category_t _OBJC_$_CATEGORY_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
    "People",
    0, // &OBJC_CLASS_$_People,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak,
    0,
    0,
    0,
};
static void OBJC_CATEGORY_SETUP_$_People_$_Speak(void ) {
    _OBJC_$_CATEGORY_People_$_Speak.cls = &OBJC_CLASS_$_People;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
    (void *)&OBJC_CATEGORY_SETUP_$_People_$_Speak,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_People_$_Speak,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我們看到最后會轉成 _category_t 這么一個結構體

2.1 分類的結構
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; //屬性列表
};

通過分類結構我們可以看到,分類里可以添加實例方法,類方法,遵循協議,定義屬性。我們看到當我們編譯完一個分類,它的所有信息都放到下面這個結構體中了

static struct _category_t _OBJC_$_CATEGORY_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
    "People",
    0, // &OBJC_CLASS_$_People,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak,
    0,
    0,
    0,
};

分類底層結構就是個結構體,但是怎么讓一個類的對象調用它的方法呢,下面我們來看一下。

三 Category的加載處理流程

分類的加載處理流程主要有下面三步:

1.通過Runtime加載某個類的所有Category數據
2.把所有Category的方法、屬性、協議數據,合并到一個大數組中 后面參與編譯的Category數據,會在數組的前面
3.將合并后的分類數據(方法、屬性、協議),插入到類原來數據的前面

下面我們通過源碼,來看它每一步都是如何實現的。
首先我們從runtime初始化函數開始看

步驟1
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);
}

步驟2
接著我們來到 &map_images讀取模塊(images這里代表模塊),來到map_images_nolock函數中找到_read_images函數,在_read_images函數中我們找到分類相關代碼
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);
}


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

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{

   *****省略******
   // 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);
                }
            }
        }
    }
}
通過_getObjc2CategoryList函數獲取到分類列表之后,
進行遍歷,獲取其中的方法,協議,屬性等。
可以看到最終都調用了remethodizeClass(cls);函數。我們來到remethodizeClass(cls)

步驟3
attachCategories函數接收了類對象cls和分類數組cats,
如我們一開始寫的代碼所示,一個類可以有多個分類。
之前我們說到分類信息存儲在category_t結構體中,
那么多個分類則保存在category_list中

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

    runtimeLock.assertWriting();

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

步驟4  
1 首先根據方法列表,屬性列表,協議列表,malloc分配內存,
根據多少個分類以及每一塊方法需要多少內存來分配相應的內存地址。
2 然后從分類數組里面往三個數組里面存放分類數組里面存放的分類方法,
屬性以及協議放入對應mlist、proplists、protolosts數組中,
這三個數組放著所有分類的方法,屬性和協議。

3 之后通過類對象的data()方法,拿到類對象的class_rw_t結構體rw,
在class結構中我們介紹過,class_rw_t中存放著類對象的方法,屬性和協議等數據,rw結構體通過類對象的data方法獲取,
所以rw里面存放這類對象里面的數據。

4 最后分別通過rw調用方法列表、屬性列表、協議列表的attachList函數,
將所有的分類的方法、屬性、協議列表數組傳進去,
我們可以猜測在attachList方法內部將分類和本類相應的對象方法,屬性,和協議進行了合并

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; //將所有分類中的所有方法存入mlists [ [method_t,method_t] [method_t,method_t] ...... ]
            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;
        }
    }
     //取出類對象
    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);
    free(protolists);
}

步驟5  方法合并

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

我們可以看到分類的方法屬性協議會追加到原來類的方法屬性協議列表的前面,這也就是說如果一個類和它的分類有相同的方法,它的分類的方法會先被調用
上面的源碼跟讀完了,主要源碼流程如下:
源碼解讀順序
objc-os.mm
_objc_init
map_images
map_images_nolock
objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy
到此我們總結下category整個流程:我們每創建一個分類,在編譯時都會生成category_t這樣一個結構體并將分類的方法列表等信息存入_category_t這個結構體。在編譯階段分類的相關信息和本類的相關信息是分開的。等到運行階段,會通過runtime加載某個類的所有Category數據,把所有Category的方法、屬性、協議數據分別合并到一個數組中,然后再將分類合并后的數據插入到本類的數據的前面
到此分類原理就講完了,接下來我們再講解下category中的兩個方法。

四 Category的load方法講解
4.1 load基本使用

我們來看一下load基本使用示例

#import <Foundation/Foundation.h>

@interface People : NSObject

@end

#import "People.h"

@implementation People

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

@end
#import "People.h"

@interface People (Speak)

@end

#import "People+Speak.h"

@implementation People (Speak)

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

@end
#import "People.h"

@interface People (Eat)

@end

#import "People+Eat.h"

@implementation People (Eat)


+ (void)load {
    NSLog(@"%s",__func__);
}
@end
#import "People.h"

@interface SubPeople : People

@end


#import "SubPeople.h"

@implementation SubPeople

+ (void)load
{
    NSLog(@"%s",__func__);
}
@end
#import <Foundation/Foundation.h>
//#import "People.h"
//#import "People+Speak.h"
//#import "People+Eat.h"
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        People *people = [[People alloc] init];
    }
    return 0;
}

output:
2019-02-07 20:53:50.896337+0800 thread[3442:174464] +[People load]
2019-02-07 20:53:50.896816+0800 thread[3442:174464] +[SubPeople load]
2019-02-07 20:53:50.896910+0800 thread[3442:174464] +[People(Speak) load]
2020-02-07 20:53:50.896971+0800 thread[3442:174464] +[People(Eat) load]

我們可以發現,我僅僅重寫實現了load方法,只要編譯完運行,就會調用load方法。即使我沒有任何引用使用的地方。為什么呢,接下來我們看一下

4.2 load調用原理

+load方法會在runtime加載類、分類時調用
每個類、分類的+load,在程序運行過程中只調用一次

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

    // Call +load methods (without runtimeLock - re-entrant)
    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) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        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;
}

因為在程序運行時就調用,所以我們也是從runtime初始化方法開始,load調用順序如下

4.3調用順序
1.先調用類的+load

按照編譯先后順序調用(先編譯,先調用)
調用子類的+load之前會先調用父類的+load

2.再調用分類的+load

按照編譯先后順序調用(先編譯,先調用)

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
        more_categories = call_category_loads(); //再調用分類的+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;
}

/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
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++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;  //取出classes的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);
}

+load方法是根據方法地址直接調用,并不是經過objc_msgSend函數調用的。下面我們再看一下initialize方法。如果是手動調用load,[people load],那就會走消息發送機制。

五 Category的initialize方法講解
5.1 initialize基本使用

我們把上面load的例子直接改成initialize就好了,如下:

#import <Foundation/Foundation.h>

@interface People : NSObject

@end

#import "People.h"

@implementation People

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

@end
#import "People.h"

@interface People (Speak)

@end

#import "People+Speak.h"

@implementation People (Speak)

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

@end
#import "People.h"

@interface People (Eat)

@end

#import "People+Eat.h"

@implementation People (Eat)

+ (void) initialize {
    NSLog(@"%s",__func__);
}
@end
#import "People.h"

@interface SubPeople : People

@end

#import "SubPeople.h"

@implementation SubPeople

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

#import <Foundation/Foundation.h>
#import "People.h"
#import "People+Speak.h"
#import "People+Eat.h"
#import "SubPeople.h"
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SubPeople *people = [[SubPeople alloc] init];
        SubPeople *people2 = [[SubPeople alloc] init];
    }
    return 0;
}

output:
2019-02-07 20:49:47.531310+0800 thread[3376:171866] +[People(Eat) initialize]
2019-02-07 20:49:47.531748+0800 thread[3376:171866] +[SubPeople initialize]

最終輸出結果是People eat分類的initialize方法,然后又調用SubPeople自己的initialize方法,我們生成了兩個對象,結果輸出結果就一次,這是為什么呢,我們接著來看。

5.2 initialize調用原理
+initialize方法會在類第一次接收到消息時調用

源碼調用流程如下:
因為是在類第一次接收到消息時調用,那肯定是在objc_msgSend方法內部調用的,但是objc_msgSend是匯編代碼,它的實現在objc-msg-arm64.s文件下,它的深層實現我們是看不到的,但是既然是通過消息機制,那么根據消息發送流程,首先要找到方法,然后才能調用方法,那我們就看一下在查找方法或者調用方法時有沒有調用initialize,首先我們看查找方法

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
     ----- 省略------
    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // 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
    }
 ----- 省略------

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    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);  //初始化父類
    }
    
    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {    //如果自己未初始化
            cls->setInitializing();   //初始化自己
            reallyInitialize = YES;
        }
    }
    
    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            // LOL JK we don't really call +initialize methods after fork().
            performForkChildInitialize(cls, supercls);
            return;
        }
        
        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                         pthread_self(), cls->nameForLogging());
        }

        // Exceptions: A +initialize call that throws an exception 
        // is deemed to be a complete and successful +initialize.
        //
        // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
        // bootstrapping problem of this versus CF's call to
        // objc_exception_set_functions().
#if __OBJC2__
        @try
#endif
        {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
        }
}
    
}

// 發送Initialize消息
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

我們跟蹤源碼發現在查找方法的過程中就已經處理過initialize方法了。
根據最后的源碼我們可以看出initialize調用順序從如下:

1 先調用父類的+initialize,再調用子類的+initialize
2 先初始化父類,再初始化子類,每個類只會初始化1次

到這里講完,我們也就能解釋基本示例里面的輸出結果了,因為initialize只會調用一次,所以在發送第二個alloc消息就不會在調用了,他先輸出父類分類Eat的initialize打印方法(先調用分類的相同方法),然后輸出自己的initialize方法。OK這里initialize也講完了,最后我們再對比一下這兩個方法

load、initialize的區別

調用方式:load是根據函數地址直接調用,initialize是通過objc_msgSend調用
調用時刻:load是runtime加載類、分類的時候調用(只會調用1次),initialize是類第一次接收到消息的時候調用,每一個類只會initialize一次(父類的initialize方法可能會被調用多次)

調用順序:先調用類的load方法,先編譯那個類,就先調用load。在調用load之前會先調用父類的load方法。分類中load方法不會覆蓋本類的load方法,先編譯的分類優先調用load方法。initialize先初始化父類,之后再初始化子類。如果子類沒有實現+initialize,會調用父類的+initialize(所以父類的+initialize可能會被調用多次),如果分類實現了+initialize,就覆蓋類本身的+initialize調用。
OK 到此category就講完了。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容