Category底层原理

image

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

分类的使用是非常简单的,为什么给一个类添加的分类而且分类的方法和原有的方法的调用方式是一模一样的都能通过这个类的对象进行调用呢,我们一起来探究一下。

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

分类底层结构就是个结构体,但是怎么让一个类的对象调用它的方法呢,下面我们来看一下。

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这有个iOS交流群:642363427,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术,iOS开发者一起交流学习成长!

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

我们可以发现,我仅仅重写实现了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;
}

最终输出结果是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调用。

推荐👇:

如果你想一起进阶,了解最新面试行情不妨添加一下交流群642363427

OK 到此category就讲完了。

原文地址:https://juejin.cn/post/6897488347913650190

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容