深入理解 Objective-C ☞ Category

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

内容简介:日常开发中经常会用到 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 方法


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

查看所有标签

猜你喜欢:

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

Data Structures and Algorithm Analysis in Java

Data Structures and Algorithm Analysis in Java

Mark A. Weiss / Pearson / 2011-11-18 / GBP 129.99

Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course wa......一起来看看 《Data Structures and Algorithm Analysis in Java》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器