Category底層原理

一 Category基本使用
二 Category的底層結(jié)構(gòu)
三 Category的加載處理流程
四 Category的load方法講解
五 Category的initialize方法講解

Category也叫分類或類別,是OC提供的一種擴(kuò)展類的方式。不管是自定義的類還是系統(tǒng)的類,我們都可以通過(guò)Category給原有類擴(kuò)展方法(實(shí)例方法和類方法都可以),而且擴(kuò)展的方法和原有的方法的調(diào)用方式是一模一樣的。比如我項(xiàng)目中經(jīng)常需要統(tǒng)計(jì)一個(gè)字符串中字母的個(gè)數(shù),但是系統(tǒng)沒(méi)有提供這個(gè)方法,那我們就可以用Category給NSString類擴(kuò)展一個(gè)方法,然后只需引入Category的頭文件就可以和調(diào)用系統(tǒng)方法一樣來(lái)調(diào)用擴(kuò)展的方法

一 Category基本使用

下面我們來(lái)看一下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

分類的使用是非常簡(jiǎn)單的,為什么給一個(gè)類添加的分類而且分類的方法和原有的方法的調(diào)用方式是一模一樣的都能通過(guò)這個(gè)類的對(duì)象進(jìn)行調(diào)用呢,我們一起來(lái)探究一下。

二 Category的底層結(jié)構(gòu)

我們把People+Speak.m底層轉(zhuǎn)換成C++來(lái)看一下
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;
//根據(jù)分類的定義,給_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 };

我們看到最后會(huì)轉(zhuǎn)成 _category_t 這么一個(gè)結(jié)構(gòu)體

2.1 分類的結(jié)構(gòu)
struct category_t {
    const char *name;  //類名稱
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; //對(duì)象方法列表
    const struct _method_list_t *class_methods;//類方法列表
    const struct _protocol_list_t *protocols; //協(xié)議列表
    const struct _prop_list_t *properties; //屬性列表
};

通過(guò)分類結(jié)構(gòu)我們可以看到,分類里可以添加實(shí)例方法,類方法,遵循協(xié)議,定義屬性。我們看到當(dāng)我們編譯完一個(gè)分類,它的所有信息都放到下面這個(gè)結(jié)構(gòu)體中了

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

分類底層結(jié)構(gòu)就是個(gè)結(jié)構(gòu)體,但是怎么讓一個(gè)類的對(duì)象調(diào)用它的方法呢,下面我們來(lái)看一下。

三 Category的加載處理流程

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

1.通過(guò)Runtime加載某個(gè)類的所有Category數(shù)據(jù)
2.把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個(gè)大數(shù)組中 后面參與編譯的Category數(shù)據(jù),會(huì)在數(shù)組的前面
3.將合并后的分類數(shù)據(jù)(方法、屬性、協(xié)議),插入到類原來(lái)數(shù)據(jù)的前面

下面我們通過(guò)源碼,來(lái)看它每一步都是如何實(shí)現(xiàn)的。
首先我們從runtime初始化函數(shù)開(kāi)始看

步驟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
接著我們來(lái)到 &map_images讀取模塊(images這里代表模塊),來(lái)到map_images_nolock函數(shù)中找到_read_images函數(shù),在_read_images函數(shù)中我們找到分類相關(guān)代碼
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);
                }
            }
        }
    }
}
通過(guò)_getObjc2CategoryList函數(shù)獲取到分類列表之后,
進(jìn)行遍歷,獲取其中的方法,協(xié)議,屬性等。
可以看到最終都調(diào)用了remethodizeClass(cls);函數(shù)。我們來(lái)到remethodizeClass(cls)

步驟3
attachCategories函數(shù)接收了類對(duì)象cls和分類數(shù)組cats,
如我們一開(kāi)始寫的代碼所示,一個(gè)類可以有多個(gè)分類。
之前我們說(shuō)到分類信息存儲(chǔ)在category_t結(jié)構(gòu)體中,
那么多個(gè)分類則保存在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 首先根據(jù)方法列表,屬性列表,協(xié)議列表,malloc分配內(nèi)存,
根據(jù)多少個(gè)分類以及每一塊方法需要多少內(nèi)存來(lái)分配相應(yīng)的內(nèi)存地址。
2 然后從分類數(shù)組里面往三個(gè)數(shù)組里面存放分類數(shù)組里面存放的分類方法,
屬性以及協(xié)議放入對(duì)應(yīng)mlist、proplists、protolosts數(shù)組中,
這三個(gè)數(shù)組放著所有分類的方法,屬性和協(xié)議。

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

4 最后分別通過(guò)rw調(diào)用方法列表、屬性列表、協(xié)議列表的attachList函數(shù),
將所有的分類的方法、屬性、協(xié)議列表數(shù)組傳進(jìn)去,
我們可以猜測(cè)在attachList方法內(nèi)部將分類和本類相應(yīng)的對(duì)象方法,屬性,和協(xié)議進(jìn)行了合并

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

    bool isMeta = cls->isMetaClass();
    //根據(jù)每個(gè)分類中的方法列表,屬性列表,協(xié)議列表分配內(nèi)存
    // 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];  //遍歷分類數(shù)組

        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;
        }
       //所有協(xié)議
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
     //取出類對(duì)象
    auto rw = cls->data();
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    將所有分類的對(duì)象方法,附加到類對(duì)象的方法列表
    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])); //原數(shù)據(jù)后移
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0])); //拷貝新數(shù)據(jù)到空出來(lái)的內(nèi)存
        }
        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]));
        }
    }

我們可以看到分類的方法屬性協(xié)議會(huì)追加到原來(lái)類的方法屬性協(xié)議列表的前面,這也就是說(shuō)如果一個(gè)類和它的分類有相同的方法,它的分類的方法會(huì)先被調(diào)用
上面的源碼跟讀完了,主要源碼流程如下:
源碼解讀順序
objc-os.mm
_objc_init
map_images
map_images_nolock
objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy
到此我們總結(jié)下category整個(gè)流程:我們每創(chuàng)建一個(gè)分類,在編譯時(shí)都會(huì)生成category_t這樣一個(gè)結(jié)構(gòu)體并將分類的方法列表等信息存入_category_t這個(gè)結(jié)構(gòu)體。在編譯階段分類的相關(guān)信息和本類的相關(guān)信息是分開(kāi)的。等到運(yùn)行階段,會(huì)通過(guò)runtime加載某個(gè)類的所有Category數(shù)據(jù),把所有Category的方法、屬性、協(xié)議數(shù)據(jù)分別合并到一個(gè)數(shù)組中,然后再將分類合并后的數(shù)據(jù)插入到本類的數(shù)據(jù)的前面
到此分類原理就講完了,接下來(lái)我們?cè)僦v解下category中的兩個(gè)方法。

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

我們來(lái)看一下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]

我們可以發(fā)現(xiàn),我僅僅重寫實(shí)現(xiàn)了load方法,只要編譯完運(yùn)行,就會(huì)調(diào)用load方法。即使我沒(méi)有任何引用使用的地方。為什么呢,接下來(lái)我們看一下

4.2 load調(diào)用原理

+load方法會(huì)在runtime加載類、分類時(shí)調(diào)用
每個(gè)類、分類的+load,在程序運(yùn)行過(guò)程中只調(diào)用一次

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);
}
//跟進(jìn)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();
}
//跟進(jìn)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;
}

因?yàn)樵诔绦蜻\(yùn)行時(shí)就調(diào)用,所以我們也是從runtime初始化方法開(kāi)始,load調(diào)用順序如下

4.3調(diào)用順序
1.先調(diào)用類的+load

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

2.再調(diào)用分類的+load

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

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) { //先調(diào)用類的+load
            call_class_loads();
        }

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

/***********************************************************************
* 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); //直接調(diào)用
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

+load方法是根據(jù)方法地址直接調(diào)用,并不是經(jīng)過(guò)objc_msgSend函數(shù)調(diào)用的。下面我們?cè)倏匆幌耰nitialize方法。如果是手動(dòng)調(diào)用load,[people load],那就會(huì)走消息發(fā)送機(jī)制。

五 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]

最終輸出結(jié)果是People eat分類的initialize方法,然后又調(diào)用SubPeople自己的initialize方法,我們生成了兩個(gè)對(duì)象,結(jié)果輸出結(jié)果就一次,這是為什么呢,我們接著來(lái)看。

5.2 initialize調(diào)用原理
+initialize方法會(huì)在類第一次接收到消息時(shí)調(diào)用

源碼調(diào)用流程如下:
因?yàn)槭窃陬惖谝淮谓邮盏较r(shí)調(diào)用,那肯定是在objc_msgSend方法內(nèi)部調(diào)用的,但是objc_msgSend是匯編代碼,它的實(shí)現(xiàn)在objc-msg-arm64.s文件下,它的深層實(shí)現(xiàn)我們是看不到的,但是既然是通過(guò)消息機(jī)制,那么根據(jù)消息發(fā)送流程,首先要找到方法,然后才能調(diào)用方法,那我們就看一下在查找方法或者調(diào)用方法時(shí)有沒(méi)有調(diào)用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;  //是否初始化過(guò)

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) { //如果父類未初始化過(guò)
        _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());
            }
        }
}
    
}

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

我們跟蹤源碼發(fā)現(xiàn)在查找方法的過(guò)程中就已經(jīng)處理過(guò)initialize方法了。
根據(jù)最后的源碼我們可以看出initialize調(diào)用順序從如下:

1 先調(diào)用父類的+initialize,再調(diào)用子類的+initialize
2 先初始化父類,再初始化子類,每個(gè)類只會(huì)初始化1次

到這里講完,我們也就能解釋基本示例里面的輸出結(jié)果了,因?yàn)閕nitialize只會(huì)調(diào)用一次,所以在發(fā)送第二個(gè)alloc消息就不會(huì)在調(diào)用了,他先輸出父類分類Eat的initialize打印方法(先調(diào)用分類的相同方法),然后輸出自己的initialize方法。OK這里initialize也講完了,最后我們?cè)賹?duì)比一下這兩個(gè)方法

load、initialize的區(qū)別

調(diào)用方式:load是根據(jù)函數(shù)地址直接調(diào)用,initialize是通過(guò)objc_msgSend調(diào)用
調(diào)用時(shí)刻:load是runtime加載類、分類的時(shí)候調(diào)用(只會(huì)調(diào)用1次),initialize是類第一次接收到消息的時(shí)候調(diào)用,每一個(gè)類只會(huì)initialize一次(父類的initialize方法可能會(huì)被調(diào)用多次)

調(diào)用順序:先調(diào)用類的load方法,先編譯那個(gè)類,就先調(diào)用load。在調(diào)用load之前會(huì)先調(diào)用父類的load方法。分類中l(wèi)oad方法不會(huì)覆蓋本類的load方法,先編譯的分類優(yōu)先調(diào)用load方法。initialize先初始化父類,之后再初始化子類。如果子類沒(méi)有實(shí)現(xiàn)+initialize,會(huì)調(diào)用父類的+initialize(所以父類的+initialize可能會(huì)被調(diào)用多次),如果分類實(shí)現(xiàn)了+initialize,就覆蓋類本身的+initialize調(diào)用。
OK 到此category就講完了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,553評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,620評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,416評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,940評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,170評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,597評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,029評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,407評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,663評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,403評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,746評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容