美文网首页
解读objc源码:解剖Category

解读objc源码:解剖Category

作者: ElaineYin | 来源:发表于2018-06-07 18:22 被阅读11次

1. Category简介

category是Objective-C 2.0之后添加的语言特性,category的一般应用场景有下面几个,一般最常用的是前三个:

  • 为已经存在的类添加方法
    Category可以添加方法,可以通过关键字关联添加属性,不能添加成员变量。
  • 把类的实现分开在几个不同的文件里面
    好处是:1、可以减少单个文件的体积 2、可以把不同的功能组织到不同的category里 3、可以由多个开发者共同完成一个类 4、可以按需加载想要的category等
  • 声明私有方法
  • 模仿多继承
  • 把framework的私有方法分开

2. Category和Extension的区别

extension并不是匿名的category,它们是完全不同的

  1. category是运行期决议的,extension在编译器决议是类的一部分,在编译器和头文件的@interface和实现文件里的@implement一起形成了一个完整的类。伴随着类的产生而产生,也随着类的消失而消失。
  2. extension一般用来隐藏类的私有消息,你必须有一个类的源码才能添加一个类的extension,所以对于系统一些类,如nsstring,就无法添加类扩展,而Category,你无需知道类的源码,可以重写类的方法
  3. extension可以添加实例变量,category不能添加实例变量

3. objc源码中的category

3.1 先开看下category的struct
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;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)
至于原因,可以看上一篇,从类的结构分析为什么不能添加变量,可以添加属性和方法。

4. Category添加过程

4.1 怎样把category中的添加的方法添加到类中?

void attachCategories(Class cls, category_list *cats, bool flush_caches):把category中的添加的方法、属性、协议添加到类中,这里cats是category_t类型数组,一个类可能会添加多个category。(这里attachCategories函数只截取方法添加部分,以方法添加为例,属性和协议添加类似)

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    /// 申请空间malloc,这里的mlists数组中包含的对象是指针,指针指向category的methodlist
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    
    int mcount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {///get newest categories first,最新的放在第一个
        auto& entry = cats->list[i];
        /// 获取到category方法列表的指针
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            /// 把指向方法列表的指针放入申请的数组中
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
    }
    /// 获取类的实例
    auto rw = cls->data();
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    ///attachLists方法:把保存的所有category的方法添加到rw->methods中
    rw->methods.attachLists(mlists, mcount);

    /// 释放空间
    free(mlists);
}

从函数中可以很清晰的看到,category中的方法添加到类的一个过程:先申请sizeof为cats->count的一个指针数组空间,把所有category的方法列表的指针存到这个数组(最新的在第一位),之后rw->methods.attachLists(mlists, mcount);把这个包含方法列表指针的数组添加到类原来的方法列表里,从而实现了category中添加方法的功能。

4.2 Category重写方法,会覆盖原来类的方法吗?

如果你有在Category中重写过方法,应该会很清楚,调用该重写方法的时候会调用Category中的,应该也会考虑过:

  • Category重写方法是覆盖了原来类的方法吗?
  • 还能不能调用到原来的方法?

我们来看下把category中的方法添加到类的方法列表的具体实现应该能解答这两个疑惑:
来看下rw->methods.attachLists(mlists, mcount);的实现过程(篇幅过长,只截取部分代码):

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;

            /// realloc:对内存进行大小的调整
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            /// array()的count
            array()->count = newCount;
            /// memmove函数:将原来的lists拷贝到最后面
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            /// 把添加的list放到前面
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        ...
    }

我有在函数里面添加了部分注释,大家应该能看明白,简单来说就是:

1. Category中的方法列表会添加到类方法列表的前面,所以并没有覆盖原来的方法。

2. 至于为什么会调用category中的方法,是因为当方法调用时,会从类的方法列表中查找方法的实现,Category的方法在列表的前面,所以会被调用。

3. 类的方法没被覆盖,所以我们还是可以调用到类的原来的方法的,只要从方法列表中最后开始查找,找到的第一个就是类的方法了。

相关文章

网友评论

      本文标题:解读objc源码:解剖Category

      本文链接:https://www.haomeiwen.com/subject/chqnsftx.html