Category

作者: 纳兰沫 | 来源:发表于2019-12-02 14:03 被阅读0次

Category的使用场合是什么

拆解模块

Category的实现原理

Category的底层结构是struct category_t 里面存储着分类的对象方法 类方法 属性 协议
在程序运行的时候 runtime会将Category的数据 合并到类信息中(类对象、元类对象中)

Category 和 Extension的区别是什么

Class Extension 在编译的时候 他的数据就已经包含在类信息中
Category 是在运行时 才会将数据合并到类信息中

Category有load方法吗? load方法是什么时候调用的? load方法能继承吗?

有load方法
load方法在runtime加载类 分类的时候调用
load方法可以继承 但是一般情况下 不会主动去调用load方法 都是让系统自动调用

load initialize方法的区别是什么 他们在category中的调用顺序是什么 以及出现继承的时候他们的调用过程

区别
1.调用方式
  a.load是根据函数地址直接调用
  b.initialize是通过objc_msgSend调用
2.调用时刻
  a.load是runtime加载类 分类的时候调用
  b.initialize是类第一次接受到消息的时候调用 每一个类只会initialize一次 (父类的initialize方法可能会被调用多次)
调用顺序
load
 - 先调用类的load
   a.先编译的类 优先调用load
   b.调用子类的load之前 会先调用父类的load
 - 再调用分类的load
   a.先编译的分类 优先调用load
initialize
 -  先初始化父类
 -  再初始化子类 (可能最终调用的是父类的initialize方法)

Category能否添加成员变量 如果可以 如何给Category添加成员变量

不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果

Catgory的底层结构

Catgory的底层结构.png
- 编译时 所有分类的数据都会存放在_category_t的结构体中 
- 一个分类就相当于一个结构体 有多少分类就有多少对应的_category_t结构体
- 运行时 通过runtime动态将分类的方法合并到类对象 元类对象中
- 最后参与编译的分类 放在最前面 他的方法在方法列表的最前面 最先被调用

Category的加载处理过程

1.通过Runtime加载某个类的所有Category数据
2.把所有Category的方法 属性 协议数据 合并到一个大数组中 
  后面参与编译的Category数据 会在数组的前面(先对数组进行扩容 原先的大小+分类里面的列表的大小 然后把原先
放在方法列表的里面往后挪动,挪动到最后 把前面的位置空出来,把分类里面的方法列表放在原先的方法列表的前面)
3.将合并后的分类数据 (方法 属性 协议) 插入到原来数据的前面
**最后编译的放在最前面 所以 会被先调用**

一个类只有一个类对象

memove memcopy区别

- memove 根据传进去的参数先判断往哪个方向挪动 判断挪数据是哪个数据先开始挪动(可以保证数据可以完整的挪动过去)
- memcopy 一个个的拷贝 从小地址开始 一个个的挪动

load方法

不管这个类是否被调用 都会加载这个load方法

+load 方法会在runtime加载类 分类时调用
每个类、分类的+load,在程序运行过程中只调用一次
load方法调用顺序
1.先调用类的+load
  按照编译先后顺序调用(先编译,先调用)
  调用子类的+load之前会先调用父类的+load
2.再调用分类的+load
  按照编译先后顺序调用(先编译,先调用)

load方法的调用是根据方法地址直接调用 不是经过objc_msgSend函数调用

initialize方法

initialize方法 是类第一次接受到消息的时候调用
initialize方法调用顺序
- 先调用父类的+initialize,再调用子类的+initialize
- (先初始化父类,再初始化子类,每个类只会初始化1次)

如果分类实现了+ initialize 方法 就会覆盖类本事的initialize 调用
如果子类没有实现initialize 会调用父类的initialize (所以 父类的initialize可能会被调用多次)

initialize会先调用父类的initialize方法 是因为它内部有主动调用父类的initialize(有继承关系和父类还没有调用initialize方法的前提下)

关联对象

使用全局变量实现setter方法
缺点 因为全局变量的原因 会造成所有的对象都是一个属性值 值是一样的
使用全局的字典实现setter方法
缺点 线程安全 内存泄露问题  不同的线程访问字典出现线程安全
如果出现多个属性 就要出现多个字典 代码复杂

设置关联对象

设置key为const void *MJNameKey;

因为未设置值 就代表key是NULL 多个key都是NULL 出现相应问题

const void *MJNameKey;

- (void)setName:(NSString *)name{

    //id  _Nonnull object 对象 关联对象
    //const void * _Nonnull key  指针
    //id  _Nullable value  关联的值
    //objc_AssociationPolicy policy  关联策略
    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)name{

    return objc_getAssociatedObject(self, MJNameKey);
}
设置key为const void *MJNameKey = &MJNameKey;

未设置为static 外部可以访问改变

const void *MJNameKey = &MJNameKey;

- (void)setName:(NSString *)name{

    //id  _Nonnull object 对象 关联对象
    //const void * _Nonnull key  指针
    //id  _Nullable value  关联的值
    //objc_AssociationPolicy policy  关联策略
    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)name{

    return objc_getAssociatedObject(self, MJNameKey);
}
设置key为static const void *MJNameKey = &MJNameKey;

使得该key只能在当前文件使用

static const void *MJNameKey = &MJNameKey;

- (void)setName:(NSString *)name{

    //id  _Nonnull object 对象 关联对象
    //const void * _Nonnull key  指针
    //id  _Nullable value  关联的值
    //objc_AssociationPolicy policy  关联策略
    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)name{

    return objc_getAssociatedObject(self, MJNameKey);
}
设置key为static const char MJNameKey;

只占用一个字节 节省空间

static const char MJNameKey;

- (void)setName:(NSString *)name{

    //id  _Nonnull object 对象 关联对象
    //const void * _Nonnull key  指针
    //id  _Nullable value  关联的值
    //objc_AssociationPolicy policy  关联策略
    objc_setAssociatedObject(self, &MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)name{

    return objc_getAssociatedObject(self, &MJNameKey);
}
设置key为@"name"

字符串里面装的是地址值

- (void)setName:(NSString *)name{

    //id  _Nonnull object 对象 关联对象
    //const void * _Nonnull key  指针
    //id  _Nullable value  关联的值
    //objc_AssociationPolicy policy  关联策略
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)name{

    return objc_getAssociatedObject(self, @"name");
}
设置key为宏定义
#define MJNameKey @"name"
- (void)setName:(NSString *)name{

    //id  _Nonnull object 对象 关联对象
    //const void * _Nonnull key  指针
    //id  _Nullable value  关联的值
    //objc_AssociationPolicy policy  关联策略
    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)name{

    return objc_getAssociatedObject(self, MJNameKey);
}
设置key为IMP
- (void)setName:(NSString *)name{

    //id  _Nonnull object 对象 关联对象
    //const void * _Nonnull key  指针
    //id  _Nullable value  关联的值
    //objc_AssociationPolicy policy  关联策略
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)name{

    return objc_getAssociatedObject(self, @selector(name));
}
设置key为IMP 同时在getName里面设置为_cmd
- (void)setName:(NSString *)name{

    //id  _Nonnull object 对象 关联对象
    //const void * _Nonnull key  指针
    //id  _Nullable value  关联的值
    //objc_AssociationPolicy policy  关联策略
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)name{

    return objc_getAssociatedObject(self, _cmd);
}

关联策略

关联策略.png

关联对象的原理

关联对象的核心对象
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
关联对象的核心对象.png 关联对象的核心对象之间的关系.png
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)

其中object 是AssociationsHashMap的key 也就是disguised_ptr_t
key存放在ObjectAssociationMap里面 是里面的key
value  policy 是在ObjcAssociation里面存放

关联对象并不是存储在被关联对象本身内存中 关联对象存储在全局的统一的一个AssociationsManager中
设置关联对象为nil,就相当于是移除关联对象

相关文章

网友评论

      本文标题:Category

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