从源码理解Category、load和initialize

博客链接 从源码理解Category、load和initialize

Category

Objective-C中的Category就是对装饰模式的一种具体实现。它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。

先看一段代码,后面的关于Category分析都是基于以下代码:

// 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.m

Category的底层结构

在C++代码中,我们会发现有以下关键代码:

// 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的定义如下:

// 对象方法存储
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}}
};

通过上面的一系列源码,我们知道了如下几点:

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