Category

作者: Mr_Shaozj | 来源:发表于2020-11-20 12:13 被阅读0次

1. 分类编译之后的底层结构

/**
    每一个分类,都会生成一个_category_t 的结构体
 */
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;
};

2. 分类的加载处理过程

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

3. 分类中的 + load方法

  1. +load方法会在runtime加载类、分类的时候调用
  2. 每个类、分类的load方法,在程序运行过程中,只会执行一次。
  3. 调用顺序
  • 1 . 先调用类的load方法。子类的load方法调用前,会先调用父类的load方法。
    1. 在调用分类中的load方法。
    1. 都是按照编译的先后顺序调用。
    1. load方法都是通过函数的地址直接调用,并不是通过objc_msgSend函数调用。

4. initialize

  1. 会在类第一次接收到消息的时候调用。它是通过objc_msgSend() 实现的调用。
  2. 调用顺序
  • 先调用父类的initialize,再去调用子类的initialize。
  • 如果子类没有实现initialize,会调动父类的initialize,(存在父类被调用多次的情况)
  • 如果分类实现了 initialize,就会覆盖类本身的initialize的调用。

5. Category的成员变量

  1. Category是不可以直接添加成员变量。
  1. 间接添加成员变量的方法
    1. 通过全局的变量进行引用
@interface Person (Eat)
@property(nonatomic,copy)NSString * foodName;
@end

NSString * foodName_;
@implementation Person (Eat)

-(void)setFoodName:(NSString *)foodName{
    foodName_ = foodName;
}
- (NSString *)foodName{
    return foodName_;
}
该方案的问题
因为是全局的变量,会导致不同的对象,修改相同的变量时,取值错误。
    Person * p1 = [[Person alloc] init];
    p1.foodName = @"p1-- foodName";
    NSLog(@"%@",p1.foodName);

    Person * p2 = [[Person alloc] init];
    p2.foodName = @"p2-- foodName";
    
    NSLog(@"%@",p1.foodName);
结果会成为   p2-- foodName
    1. 通过字典的方式
NSMutableDictionary * personEatDic;
@implementation Person (Eat)

+(void)load{
    personEatDic = [NSMutableDictionary dictionaryWithCapacity:1];
}
-(void)setFoodName:(NSString *)foodName{
    NSString * selfIsa = [NSString stringWithFormat:@"%p",self];
    personEatDic[selfIsa] = foodName;
}
- (NSString *)foodName{
    NSString * selfIsa = [NSString stringWithFormat:@"%p",self];
    return personEatDic[selfIsa];
}
该方案的不足
每次添加一个属性,就需要全局声明一个字段来进行存储。
    1. 关联对象的方法 runtime
//以当前的内存地址作为key, static 仅限本类的.m 文件可以访问
static const void * MYPersonKey = &MYPersonKey;
@implementation Person (Eat)

-(void)setFoodName:(NSString *)foodName{
    
    //objc_AssociationPolicy 关联的策略,跟属性的修饰符保持一致
    /**
     OBJC_ASSOCIATION_ASSIGN = 0,
     OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
     OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
     OBJC_ASSOCIATION_RETAIN = 01401,
     OBJC_ASSOCIATION_COPY = 01403
     */
    objc_setAssociatedObject(self, MYPersonKey, foodName, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
}

- (NSString *)foodName{
    return objc_getAssociatedObject(self, MYPersonKey);
}
----------------------------------------------------------------------------------------
//关联对象的全局变量的优化
//为考虑内存,仅需占用一个字节
static const char MYPersonKey;

-(void)setFoodName:(NSString *)foodName{

    //  &MYPersonKey 存储一个char字符的地址
    objc_setAssociatedObject(self, &MYPersonKey, foodName, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
}

- (NSString *)foodName{
    return objc_getAssociatedObject(self, &MYPersonKey);
}

6.关联对象的原理

objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)
涉及的主要类
AssociationsManager
AssociationsHashMap
AssociationsMap
ObjectAssociation
关联对象的原理

相关文章

网友评论

      本文标题:Category

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