深入理解 Objective-C ☞ Category

栏目: Objective-C · 发布时间: 1年前

来源: devhe.com

内容简介:日常开发中经常会用到 Category,对于其使用方法就不多做说明了,本篇主要介绍其底层实现原理。依然从一个例子开始,给

本文转载自:https://devhe.com/2019/02/14/深入理解-Objective-C-☞-Category/,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有。

深入理解 Objective-C ☞ Category

日常开发中经常会用到 Category,对于其使用方法就不多做说明了,本篇主要介绍其底层实现原理。

1.底层结构

1.1 编译后的结构

依然从一个例子开始,给 HHStaff 这个类创建一个分类 HHStaff+CateA ,如下所示:

// HHStaff+CateA.h
@interface HHStaff (CateA) <NSCopying, NSCoding>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger num;

- (void)methodA1;
- (void)methodA2;

+ (void)classMethodA1;
+ (void)classMethodA2;

@end

// HHStaff+CateA.m
@implementation HHStaff (CateA)

- (void)methodA1 {
    NSLog(@"这是 methodA1");
}

- (void)methodA2 {
    NSLog(@"这是 methodA2");
}

+ (void)classMethodA1 {
    NSLog(@"这是 classMethodA1");
}

+ (void)classMethodA2 {
    NSLog(@"这是 classMethodA2");
}

@end

然后终端执行 $ clang -rewrite-objc HHStaff+CateA.m 编译后,我们发现了这样一个结构:

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 在内存中的基本结构,其中包括其所属主类的类名、分类中的实例方法列表、类方法列表、协议方法列表以及属性列表。我们自己写的分类 HHStaff+CateA 编译后是这样的:

static struct _category_t _OBJC_$_CATEGORY_HHStaff_$_CateA __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
	"HHStaff",
	0, // &OBJC_CLASS_$_HHStaff,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HHStaff_$_CateA,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_HHStaff_$_CateA,
	(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_HHStaff_$_CateA,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HHStaff_$_CateA,
};

上边的 _OBJC_$_CATEGORY_HHStaff_$_CateA 就是自己添加的那个分类 HHStaff+CateA 编译后的结构,它是 struct _category_t 类型的,也就是上边提到的分类的基本结构。

下边这张图详细介绍了编译后的结构:

深入理解 Objective-C ☞ Category

1.2 OC 源码中的结构

在 objc 的源码中全局搜索 category_t, 最终在 objc-runtime-new.h 中发现了它的结构,如下所示:

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;

    // Fields below this point are not always present on disk.
    // 类属性列表
    struct property_list_t *_classProperties;
    // 获取方法列表:对象方法或类方法
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    // 获取属性列表的方法(声明):对象属性or类属性
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

上边结构中,注释行以上部分的结构与我们编译后文件中的结构体 category_t 完全一致,注释行以后部分中的 _classProperties (类属性列表) 没有接触过,后期如果遇到了再补上 O(∩_∩)O~

2.Category 合并到 Class

从上边一节我们了解到 Category 经编译后是一个与 Class 相互独立的结构,那么其中的方法、属性等信息是如何和主类联系起来的呢?其实,这一切都是在运行时发生的。下面我就开始深入 runtime 源码来探究这个相互关联的过程。

objc 的入口函数是 objc-os.mm 中的 _objc_init() ,源码如下:

void _objc_init(void)
{
    // 使用一个静态局部变量,避免了重复初始化
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // 初始化操作
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    // 注册 dyld 事件的监听
    _dyld_objc_notify_register(↦_images, load_images, unmap_image);
}

此函数的最后一行注册了 3 个 dyld 事件的监听,分别是:

map_images()
load_images()
unmap_image()

这里我们看一下 map_images() 这个函数的源码:

void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    //
    return map_images_nolock(count, paths, mhdrs);
}

这里又嵌套调用了 map_images_nolock() 这个函数,为了排除干扰,这里略去了其他代码,精简后的结构如下:

void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    // ... 此处略去 n 多字 ...

    if (hCount > 0) {
        // 读取镜像,参数:hList 即 headerList,hCount 即 headerCount。
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    // ... 此处略去 n 多字 ...
}

hCount 即头文件的个数,也就是说有新的头文件,才会执行后边的 _read_images() 函数,各参数的含义从名称就可以看得出来。

下面看看精简后的读取镜像的函数 _read_images()

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

    // Discover categories.
    for (EACH_HEADER) {

        // 1.得到一个存放着分类的地址的一维数组
        category_t **catlist = _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {

            // 2.获取数组中每一个 category_t 类型的指针及 Category 所属的主类
            category_t *cat = catlist[i];         // cat 是一个指向结构体 category_t 的指针
            Class cls = remapClass(cat->cls);     // Category 所属的主类

            // ...

            // 3.处理 Category

            // 3.1 类对象
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols ||  cat->instanceProperties)  
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);          // cls 是类对象,重新方法化,重新组织类里边的方法
                    classExists = YES;
                }
                // ...
            }

            // 3.2 元类对象
            if (cat->classMethods  ||  cat->protocols ||  (hasClassProperties && cat->_classProperties))
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());   // cls->ISA() 是元类对象,重新方法化,重新组织元类里边的方法
                }
                // ...
            }

        } // 内层 for
    } // 外层 for

    // ...
}

这里有 2 层 for 循环,首先看最外边的 for 循环,其中 EACH_HEADER 这个宏的定义为:

#define EACH_HEADER  (hIndex = 0; hIndex < hCount && (hi = hList[hIndex]); hIndex++)

也就是依次取出 hList 里的每一个 header ,执行后边的操作,后边的操作大概可以分为 3 项:

  • 取出存放着分类地址的一维数组 catlist
  • 获取 catlist 中每一个 category_t 类型的指针 cat 及 Category 所属的主类 cls
  • 处理 Category,依次针对 类对象元类对象 重新组织其中的方法,即 remethodizeClass(cls)remethodizeClass(cls->ISA())

remethodizeClass() 这个函数的作用是将 Category 中的信息添加到 class 里边,其源码如下:

// Attach outstanding categories to an existing class.
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*/))) {
        // ...
        // 将分类中的方法、属性及协议添加到 class 里边
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

上边的 if 条件语句中执行了 attachCategories() 这个函数,它的作用是将分类中的方法、属性及协议添加到 class 里边,源码如下:

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;

    // 1.整合一个类中所有分类的方法数组、属性数组和协议数组
    while (i--) {
        auto& entry = cats->list[i]; // 取出一个分类
        // 1.1 方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist; // 一次循环就将一个分类的方法列表添加到 mlists 里边,作为一个元素。
            fromBundle |= entry.hi->isBundle();
        }
        // 1.2 属性
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist; // 与上边类似
        }
        // 1.3 协议
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist; // 与上边类似
        }
    }

    // 2.将上一步整合的数组添加到类结构里边
    auto rw = cls->data();
    // 2.1 方法
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    // 2.2 属性
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    // 2.3 协议
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

这里主要做了 2 件事:

  • 整合一个类的每一个分类的方法数组(属性数组/协议数组)到一个二维数组里边,二维数组的每一个元素就是一个分类的方法列表(属性列表/协议列表);
  • 执行 class 中的 data() 方法返回一个可读可写的结构,它的类型是一个结构体 class_rw_t ,这个结构体在 上一篇 已经介绍过。然后,执行 attachLists() 函数,将上一步整合的方法数组添加到 class_rw_t 这个结构体里边。

需要注意的是,在整合那一步 while 循环中,条件是 i–,而 i 是分类的个数 (int i = cats->count;),也就是从后往前取,即先取后编译的分类。

现在我们来看上方代码中最后调用的核心方法 attachLists() :

void attachLists(List* const * addedLists, uint32_t addedCount) { // addedLists 是二维数组,addedCount 是数组元素个数 [[], [], []]
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        // 1.扩容
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); // 根据 newCount 重新分配数组
        array()->count = newCount;
        // 2.移动老数组到末尾
        memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0]));
        // 3.拷贝新数组到头部
        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]));
    }
}

此方法主要做了以下三件事(原数组:类中原来的方法(属性、协议等)数组,新数组:category中整合的方法(属性、协议)数组):

  • 原数组扩容 realloc()

深入理解 Objective-C ☞ Category

  • 移动原数组到末尾 memmove()

深入理解 Objective-C ☞ Category

  • 拷贝新数组到扩容后数组的头部 memcpy() (以方法为例)

深入理解 Objective-C ☞ Category

至此,分类中的方法、属性、协议就整合到了类结构里边。

根据前文的讨论,分类中的方法会在运行时和主类中的方法整合到一起,并且分类中的方法会放在前边,那么当查找方法的时候,就会优先查找分类的方法,如果分类和主类有相同的方法,主类中的方法就不会执行,也就是平时所说的 “覆盖” 主类方法,其实主类中的对应方法还是存在的,只是没机会执行而已。

3. 相关问题扩展

3.1 load

我们都知道 runtime 在加载类和分类时会调用 +load 方法,那么具体是怎么调用的,这就要从前文提到的回调函数 load_images() 开始说起了,这个方法的主要作用就是执行 load 方法。

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

    // 1.Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // 2.Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

这个方法主要干了 2 件事:①准备 load 方法;②执行 load 方法。下面分别介绍一下这两件事:

3.1.1 准备 load 方法

准备 load 方法的工作主要是通过下边这个函数 prepare_load_methods() 执行的:

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

    runtimeLock.assertLocked();

    classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
// 1.组织类里边的 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());
// 2.将分类添加到 loadable_list
        add_category_to_loadable_list(cat);
    }
}

上方函数总共做了两件事:①组织类里边的 load 方法;②将分类添加到 loadable_list,现在分别看看具体干了什么。

3.1.1.1 组织类里边的 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;

    // 1.递归调用当前函数
    schedule_class_load(cls->superclass);

    // 2.将含有 load 方法的类及对应的 load 方法存储到 loadable_list 里边
    add_class_to_loadable_list(cls);

    cls->setInfo(RW_LOADED);
}

这里先做了一次递归调用,然后将传入的 cls 加到 loadable_list 里边,这样做的效果是:先将父类加进 loadable_list ,再加子类。

那么具体是怎么添加的呢,下面的源码将告诉我们真相:

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    // 1.获取 load 方法,如果 cls 没有 load 方法,就不往下执行了
    method = cls->getLoadMethod();
    if (!method) return;

    // ...

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

    // 2.保存 cls 和 method 至 loadable_classes
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++; // loadable_class 的数量自加,遍历执行 load 方法时会用到
}

这里也做了两件事:

  • 查找 load 方法,源码如下,可以看出来,是通过比较字符串的方法找 load 方法的。
IMP objc_class::getLoadMethod()
{
    runtimeLock.assertLocked();

    const method_list_t *mlist;

    assert(isRealized());
    assert(ISA()->isRealized());
    assert(!isMetaClass());
    assert(ISA()->isMetaClass());

    // 便利方法列表,查找 load 方法
    mlist = ISA()->data()->ro->baseMethods();
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            // 比较字符串
            if (0 == strcmp(name, "load")) {
                return meth.imp;
            }
        }
    }

    return nil;
}
  • 将 load 方法和对应的 class 组合成 loadable_class 存储到 loadable_classes 里边。其中 loadable_classes 是个一维数组, loadable_class 是一个结构体,它的定义如下,
struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};

3.1.1.2 将分类添加到 loadable_list

添加分类至 loadable_list 是通过 add_category_to_loadable_list() 函数实现的,源码如下:

void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    // 1.获取分类中的 load 方法,如果没有,就不往下执行
    method = _category_getLoadMethod(cat);
    if (!method) return;

    // ...

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

    // 2.保存分类和 load 方法至 loadable_categories 里边
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

基本原理与上边的 add_class_to_loadable_list() 相同,区别在于这里是添加到了 loadable_categories 里边,并且被添加的是 loadable_category ,定义如下。

struct loadable_category {
    Category cat;  // may be nil
    IMP method;
};

3.1.2 执行 load 方法

前边已经准备好了 load 方法,现在就该调用了,也就是执行下边的 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();

    // *** 调用 load 方法
    do {

        // 1.调用所有类的 +load 方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2.调用所有分类的 +load 方法
        more_categories = call_category_loads();

    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);
    loading = NO;
}

此方法也是做了两件事: 调用所有 的 load 方法, 调用所有 分类 的 load 方法。

3.1.2.1 调用类的 load 方法

下边是执行类的 load 方法的函数 call_class_loads() ,主要是遍历 loadable_classes 类里边的每一个 loadable_class,取出其中存储的函数指针,直接去调用函数。也就是说,没有走 objc_msgSend() 的流程,而是直接通过函数地址调用函数,那么也就不存在 “覆盖” 的问题。

static void call_class_loads(void)
{
    int i;

    // 1.拿到 loadable_classes 及元素个数 loadable_classes_used
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;

    // 2.遍历 loadable_classes,调用每一个元素的 load 方法
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue;

        // ...

        (*load_method)(cls, SEL_load);
    }

    // Destroy the detached list.
    if (classes) free(classes);
}

3.1.2.2 调用分类的 load 方法

调用分类的 load 方法的操作都在下面的 call_category_loads() 函数里边。

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_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            // ...
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }

    // ...

    return new_categories_added;
}

仔细观察上边的函数,就会发现它和 call_class_loads() 的逻辑基本一致,这里就不多做说明了。

通过上边的讨论我们可以得出调用 +load 方法的顺序:

① 调用类的 +load

根据编译顺序调用(先编译,先调用)

调用子类的 +load 之前会先调用父类的 +load

② 调用分类的 +load
根据编译顺序调用(先编译,先调用)

3.2 initialize

众所周知, +initialize 会在类第一次接收到消息时调用,那么它到底是怎么调用的呢?

我们可以先按照这样的思路来考虑,OC 的方法调用经编译后都会变成这样的函数调用 objc_msgSend(object, @selector(method)) ,于是可以推断 objc_msgSend() 会调用 +initialize ,而且应该做了判断,如果调用过了一次,就不再重复调用。但是搜索 objc 源码后发现 objc_msgSend() 是用汇编实现的 (⊙﹏⊙)b,看着有点累。

另外,我们知道执行方法时,会先根据 isa 指针找到对应的类对象或元类对象,然后查找需要的方法。objc 的源码中有 class_getInstanceMethod()class_getClassMethod() 2 个函数,根据字面意思判断应该是获取方法的函数,那么

+initialize 应该也在这里调用,现在就以 class_getInstanceMethod() 为例,验证一下我们的推断。

下面是函数 class_getInstanceMethod() 的源码:

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

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

注意到上边的函数中有一个搜索方法列表的函数 lookUpImpOrNil() ,其实现如下(做了适当精简):

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
{
    // ...

    // 1.优先查找缓存
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    runtimeLock.lock();
    checkIsKnownClass(cls);

    if (!cls->isRealized()) {
        realizeClass(cls);
    }

    // 2.执行 initialize 方法
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst)); // 重点
        runtimeLock.lock();
    }

    // 3.后边是查找方法步骤,这里就不罗列了...
}

该方法主要做了下面几件事:

1.优先查找缓存 (Optimistic cache lookup)

2.如果需要执行 +initialize ,并且当前类没有执行过 +initialize ,就去执行 +initialize

3.后边是查找方法步骤:①查缓存;②查方法列表;③查父类的缓存和方法列表

第 2 件事执行 +initialize 方法时,调用的是 _class_initialize() 这个函数,源码如下,同样做了适当精简:

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

    Class supercls;
    bool reallyInitialize = NO;

    // 1.递归调用父类的 +initialize
    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) {
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);

    // ...

#if __OBJC2__
        @try
#endif
        {
            // 2.调用 +initialize
            callInitialize(cls);

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

这段代码说明类的 +initialize 方法的执行顺序是,先递归调用父类的 +initialize ,然后调用自己的 +initialize ,最终的执行如下所示:

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

上边最终执行的是 objc_msgSend() 这个函数,也就是说还是会根据 isa 和 super 查找方法,也就存在下边这种特殊情况了。

Note that +initialize is sent to the superclass (again) if this class doesn’t implement +initialize. 2157218

如果当前类没有实现 +initialize 方法,那么就会再调用一次父类的 +initialize 方法


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

关注码农网公众号

关注我们,获取更多IT资讯^_^


为你推荐:

相关软件推荐:

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入理解C++11

深入理解C++11

Michael Wong、IBM XL编译器中国开发团队 / 机械工业出版社 / 2013-6 / 69.00元

《深入理解C++11:C++11新特性解析与应用》内容简介:国内首本全面深入解读C++11新标准的专著,由C++标准委员会代表和IBM XL编译器中国开发团队共同撰写。不仅详细阐述了C++11标准的设计原则,而且系统地讲解了C++11新标准中的所有新语言特性、新标准库特性、对原有特性的改进,以及如何应用所有这些新特性。 《深入理解C++11:C++11新特性解析与应用》一共8章:第1章从设计......一起来看看 《深入理解C++11》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换