美文网首页
Objective-C:Category

Objective-C:Category

作者: zhouluyao | 来源:发表于2018-07-30 00:35 被阅读8次

    Category的底层结构

    Category.png

    Category的加载处理过程

    1. 通过Runtime加载某个类的所有Category数据
    2. 把所有Category的方法、属性、协议数据,合并到一个大数组中后面参与编译的Category数据,会在数组的前面
    3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

    1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA

    2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休_,殊不知后面可能还有一样名字的方法。

    +load方法

    +load方法会在runtime加载类、分类时调用每个类、分类的+load,在程序运行过程中只调用一次
    调用顺序

    1.先调用类的+load

    1. 按照编译先后顺序调用(先编译,先调用)
    2. 调用子类的+load之前会先调用父类的+load

    2.再调用category的+load

    先编译的分类,优先调用load(先编译,先调用)

    虽然对于+load的执行顺序是这样,但是对于“覆盖”掉的方法,则会先找到最后一个编译的category里的对应方法。

    +initialize方法

    +initialize方法会在类第一次接收到消息时调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
    调用顺序

    先调用父类的+initialize

    再调用子类的+initialize

    (先初始化父类,再初始化子类,每个类只会初始化1次)

    +initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点

    如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)

    如果分类实现了+initialize,就覆盖类本身的+initialize调用,类不会调用+initialize

    调用方式

    1> load是根据函数地址直接调用
    2> initialize是通过objc_msgSend调用

    在category中添加和对象关联的值?

    //添加关联对象
    void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy)
    
    //获得关联对象
    id objc_getAssociatedObject(id object, const void * key)
    
    //移除所有的关联对象
    void objc_removeAssociatedObjects(id object)
    
    @interface MJPerson (Test)
    
    @property (copy, nonatomic) NSString *name;
    
    @end
    
    #import <objc/runtime.h>
    
    @implementation MJPerson (Test)
    
    - (void)setName:(NSString *)name
    {
       objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    - (NSString *)name
    {
      // 隐式参数
      // _cmd == @selector(name)
     return objc_getAssociatedObject(self, _cmd);
    }
    //把方法地址作为KEY,也可以用其他变量作为Key
    //static修饰的变量在编译的时候值就应该确定
    static const void *NameKey = &NameKey;
    static const void *WeightKey = &WeightKey;
    
    
    void *objc_destructInstance(id obj) 
    {
        if (obj) {
            Class isa_gen = _object_getClass(obj);
            class_t *isa = newcls(isa_gen);
    
            // Read all of the flags at once for performance.
            bool cxx = hasCxxStructors(isa);
            bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);
    
            // This order is important.
            if (cxx) object_cxxDestruct(obj);
            if (assoc) _object_remove_assocations(obj);
    
            if (!UseGC) objc_clear_deallocating(obj);
        }
    
        return obj;
    }
    

    runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作

    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添加成员变量报错:Instance variables may not be placed in categories.

    category不能添加成员变量的原因:

    objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。

    methodList是是一个二维数组,所以可以修改*methodLists的值来增加成员方法.

    category可以通过关联对象的方式添加属性(property),但是不能添加实例变量(instance variable)。

    category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)

    objc_class.png

    相关文章

      网友评论

          本文标题:Objective-C:Category

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