category

作者: 轰炸机上调鸡尾酒 | 来源:发表于2017-12-06 17:01 被阅读5次

    category和extension

    1.在类中使用@property,系统会自动生成带“_”的成员变量和该变量的setter和getter方法。也就是说,属性相当于一个成员变量加getter和setter方法。

    在分类里使用@property声明属性,只是将该属性添加到该类的属性列表,但是没有生成相应的成员变量,也没有实现setter和getter方法。

    2.extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。

    3.extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。

    4.但是category则完全不一样,它是在运行期决议的。
    就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。

    category真面目

    我们知道,所有的OC类和对象,在runtime层都是用struct表示的,category也不例外,在runtime层,category用结构体category_t(在objc-runtime-new.h中可以找到此定义),它包含了
    1)、类的名字(name)
    2)、类(cls)
    3)、category中所有给类添加的实例方法的列表(instanceMethods)
    4)、category中所有添加的类方法的列表(classMethods)
    5)、category实现的所有协议的列表(protocols)
    6)、category中添加的所有属性(instanceProperties)

    typedef 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;
    } category_t;
    

    从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。事实就是category的名字用来给各种列表以及后面的category结构体本身命名,而且有static来修饰,所以在同一个编译单元里我们的category名不能重复,否则会出现编译错误。

    追本溯源-category如何加载

    OC运行时,category被附加到类上面是在map_images的时候发生的。首先
    1)把category的实例方法、协议以及属性添加到类上
    2)、把category的类方法和协议添加到类的metaclass上
    3)把类和category做一个关联映射
    4)remethodizeClass去处理,把所有category的实例方法列表拼成了一个大的实例方法列表,然后转交给了attachMethodLists方法。
    需要注意的有两点:
    1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
    2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休_,殊不知后面可能还有一样名字的方法。

    那怎么调用到原来类中被category覆盖掉的方法?
    对于这个问题,我们已经知道category其实并不是完全替换掉原来类的同名方法,只是category在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法

    -category和+load方法

    我们知道,在类和category中都可以有+load方法,那么有两个问题:
    1)、在类的+load方法调用的时候,我们可以调用category中声明的方法么?
    2)、这么些个+load方法,调用顺序是咋样的呢?
    鉴于上述几节我们看的代码太多了,对于这两个问题我们先来看一点直观的:



    我们的代码里有MyClass和MyClass的两个category (Category1和Category2),MyClass和两个category都添加了+load方法,并且Category1和Category2都写了MyClass的printName方法。

    在Xcode中点击Edit Scheme,添加如下两个环境变量(可以在执行load方法以及加载category的时候打印log信息,更多的环境变量选项可参见objc-private.h):
    运行项目,我们会看到控制台打印很多东西出来,我们只找到我们想要的信息,顺序如下:

    objc[1187]: REPLACED: -[MyClass printName] by category Category1
    objc[1187]: REPLACED: -[MyClass printName] by category Category2
    .
    .
    .
    objc[1187]: LOAD: class 'MyClass' scheduled for +load
    objc[1187]: LOAD: category 'MyClass(Category1)' scheduled for +load
    objc[1187]: LOAD: category 'MyClass(Category2)' scheduled for +load
    objc[1187]: LOAD: +[MyClass load]
    .
    .
    .
    objc[1187]: LOAD: +[MyClass(Category1) load]
    .
    .
    .
    objc[1187]: LOAD: +[MyClass(Category2) load]

    所以,对于上面两个问题,答案是很明显的:
    1)、可以调用,因为附加category到类的工作会先于+load方法的执行
    2)、+load的执行顺序是先类,后category,而category的+load执行顺序是根据编译顺序决定的。

    更上一层-category和关联对象

    如上所见,我们知道在category里面是无法为category添加实例变量的。但是我们很多时候需要在category中添加和对象关联的值,这个时候可以求助关联对象来实现。所有的关联对象都由AssociationsManager管理。

    AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。

    嗯,runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作。
    https://tech.meituan.com/DiveIntoCategory.html

    相关文章

      网友评论

          本文标题:category

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