美文网首页黑魔法
OC基础-category(4)关联对象添加成员变量

OC基础-category(4)关联对象添加成员变量

作者: 我是卖报的小行家 | 来源:发表于2021-03-06 08:57 被阅读0次

    OC基础 - 类添加成员变量
    属性 = ivar + setter(声明,实现) + getter(声明,实现)
    类扩展
    属性= 没有成员变量 + setter(声明) + getter(声明)

    因为类扩展没有成员变量,所以属性也用不到,因为不能对其进行setter以及getter方法,所以想办法给类扩展属性增加一个成员变量

    方法1 (肯定不可行啦)

    增加一个全局变量

    #import "MJPerson+Test.h"
    
    int ages_;
    
    @implementation MJPerson (Test)
    - (void)setAge:(int)age
    {
        ages_ = age;
    }
    - (int)age
    {
        return ages_;
    }
    
    @end
    
    

    这个时候存在的问题如下所示

        MJPerson * p1 = [[MJPerson alloc]init];
            p1.name = @"James";
            p1.age = 23;
           
            
            MJPerson * p2 = [[MJPerson alloc]init];
            p2.name = @"Durant";
            p2.age = 7;
            
            
            MJPerson * p3 = [[MJPerson alloc]init];
            p2.name = @"Harden";
            p2.age = 13;
            NSLog(@"p2.name = %@,age = %d",p2.name,p2.age);
            NSLog(@"p1.name = %@,age = %d",p1.name,p1.age);
            NSLog(@"p3.name = %@,age = %d",p3.name,p3.age);
    

    打印输出

    2021-03-05 11:03:53.319031+0800 CategoryAssociate[40891:1338125] p2.name = Harden,age = 13
    2021-03-05 11:03:53.319529+0800 CategoryAssociate[40891:1338125] p1.name = James,age = 13
    2021-03-05 11:03:53.319587+0800 CategoryAssociate[40891:1338125] p3.name = (null),age = 13
    

    原因分析:后面传进来的age的值会将全局变量ages_进行覆盖,所以导致之前所有的值都是最后一个传进来的值

    方法2(方法也会欠缺)

    (NSMutableDictionary) 通过键值对,每一个person有对应的自己的age,一对一关系所以为字典
    在load方法里给字典初始化,因为load方法只会被调用一次

    +(void)load
    {
        //在load里面给NSMutableDictionary *ages_;赋值,load只调用一次
        
        ages_ = [NSMutableDictionary new];
    }
    

    然后通过字典里的key取出value,这边用伪代码(思路)表示先,这样就可以暂时获取到所谓的成员变量,这个时候再写setter和getter方法

    - (void)setAge:(int)age
    {
        //通过字典里的key取出字典里的value
        //伪代码
         ages_[self] = @(age);
    }
    - (int)age
    {
       return [ages_[self] intValue];
    }
    
    

    替换掉伪代码,则整个代码如下所示
    主要是字典里的key一般都是NSString类型(用当前person类的内存地址做为key),所以self 替换成 NSString *ages = [NSString stringWithFormat:@"%p",self];
    并将其设置为一个宏,因为多处会用到

    #import "MJPerson+Test.h"
    #define MJKey [NSString stringWithFormat:@"%p",self]
    
    
    NSMutableDictionary *ages_;
    
    @implementation MJPerson (Test)
    
    +(void)load
    {
        //在load里面给NSMutableDictionary *ages_;赋值,load只调用一次
        
        ages_ = [NSMutableDictionary new];
    }
    
    - (void)setAge:(int)age
    {
        //通过字典里的key取出字典里的value
        //伪代码
       
        ages_[MJKey] = @(age);
    }
    - (int)age
    {
        return [ages_[MJKey] intValue];
    }
    
    @end
    
    

    调用

            MJPerson * p1 = [[MJPerson alloc]init];
            p1.name = @"James";
            p1.age = 23;
           
            
            MJPerson * p2 = [[MJPerson alloc]init];
            p2.name = @"Durant";
            p2.age = 7;
            
            
    
      /**
             struct MJPerson_IMPl{
                 class isa;
                 NSString *_name;
             }
             */
    
            MJPerson * p3 = [[MJPerson alloc]init];
            p3.name = @"Harden"; // @"Harden"是存储在person对象的内部 ,@"Harden" 是赋值给_name的  
            p3.age = 13; 13不是存在于person内部,13是存在于全局的字典对象里面,但是对外界来讲是没有什么区别的
    
            NSLog(@"p3.name = %@,age = %d",p3.name,p3.age);
            NSLog(@"p2.name = %@,age = %d",p2.name,p2.age);
            NSLog(@"p1.name = %@,age = %d",p1.name,p1.age);
      
    
    

    打印结果

    2021-03-05 11:29:18.562053+0800 CategoryAssociate[41418:1361256] p3.name = Harden,age = 13
    2021-03-05 11:29:18.562517+0800 CategoryAssociate[41418:1361256] p2.name = Durant,age = 7
    2021-03-05 11:29:18.562579+0800 CategoryAssociate[41418:1361256] p1.name = James,age = 23
    

    方法二总结:问题:1.线程安全(每一个person的set方法都会访问全局的字典,不同person对象,在不同线程同时访问这个字典就会造成线程安全问题),
    2.内存泄漏(因为是全局变量,而却一直在内存中)
    3.如果增加多个属性,那么就会存在多个这样的字典,就要增加其对应的setter和getter方法

    方法三:利用runtime 关联对象在category里面添加属性(重点)

    添加关联属性
    objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>);
    关联对象就是将属性(age)和类(person)关联起来
    关联策略
    objc_AssociationPolicy 对应的修饰符
    OBJC_ASSOCIATION_ASSIGN assign
    OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
    OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
    OBJC_ASSOCIATION_RETAIN strong, atomic
    OBJC_ASSOCIATION_COPY copy, atomic

    key值传自己的地址(遵从唯一不为空性)

    获得关联属性
    objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)

    移除关联属性
    objc_removeAssociatedObjects(<#id _Nonnull object#>)
    设置对象为nil,person.height = nil;移除单个的关联对象

    id object:被关联的对象
    const void *key:关联的key,要求唯一
    id value:关联的对象
    objc_AssociationPolicy policy:内存管理的策略

    上代码
    person类

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface MJPerson : NSObject
    @property (nonatomic,assign)int age;
    @end
    
    NS_ASSUME_NONNULL_END
    
    

    Person分类

    #import "MJPerson.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface MJPerson (Test)
    @property (nonatomic,copy)NSString *name;
    
    @property (nonatomic,assign)int height;
    @end
    
    #import "MJPerson+Test.h"
    #import <objc/runtime.h>
    @implementation MJPerson (Test)
    const void * MJNameKey = &MJNameKey;
    const void * MJHeightKey = &MJHeightKey;
    
    - (void)setName:(NSString *)name
    {
        objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    - (NSString *)name
    {
        return objc_getAssociatedObject(self, MJNameKey);
        
        
    }
    
    - (void)setHeight:(int)height{
        
        objc_setAssociatedObject(self, MJHeightKey, @(height), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (int)height
    {
        return  [objc_getAssociatedObject(self, MJHeightKey) intValue];
    }
    @end
    
    

    调用以及打印

     MJPerson * p1 = [[MJPerson alloc]init];
            p1.name = @"James";
            p1.age = 23;
            p1.height = 203;
            
            MJPerson * p2 = [[MJPerson alloc]init];
            p2.name = @"Durant";
            p2.age = 7;
            p2.height = 211;
    
            MJPerson * p3 = [[MJPerson alloc]init];
            p3.name = @"Harden";
            p3.age = 13;
            p3.height = 196;
           
            NSLog(@"p3.name = %@,age = %d,height = %d",p3.name,p3.age,p3.height);
            NSLog(@"p2.name = %@,age = %d,height = %d",p2.name,p2.age,p2.height);
            NSLog(@"p1.name = %@,age = %d,height = %d",p1.name,p1.age,p1.height);
    
    //打印输出
    2021-03-05 12:06:33.275907+0800 CategoryAssociate[42088:1396908] p3.name = Harden,age = 13,height = 196
    2021-03-05 12:06:33.276356+0800 CategoryAssociate[42088:1396908] p2.name = Durant,age = 7,height = 211
    2021-03-05 12:06:33.276407+0800 CategoryAssociate[42088:1396908] p1.name = James,age = 23,height = 203
    
    

    总结:有缺陷,外部可以访问到里面的全局变量(MJNameKey,MJHeightKey)的值,就可以去改这个值

    在外部直接 extern const void * MJNameKey 就可以直接访问到这个值
    (在方法三的基础上)
    优化1:
    static const void * MJNameKey = &MJNameKey;(指针变量占8个字节)
    static const void * MJHeightKey = &MJHeightKey;

    优化2:
    static const char * MJNameKey;(char只占一个字节)
    static const char * MJHeightKey;

    使用时候 &MJNameKey,&MJHeightKey,

    优化3:

    直接使用的时候
    objc_setAssociatedObject(self, @“name”, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    return objc_getAssociatedObject(self, @“name”);
    set 和get 的“name”是同一个地址(因为 “name”是放在常量区是不变的)

    所以定义一个宏

    define MJNameKey @“name”

    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    return objc_getAssociatedObject(self, MJNameKey);

    优化4:
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    return objc_getAssociatedObject(self, @selector(name));

    优化5:
    get方法里(只有get方法)
    _cmd = @selector(name);
    隐式参数
    return objc_getAssociatedObject(self, _cmd);

    接下来就是 关联对象的原理
    其实runtime是生成一个AssociationsManager类用来管理分类的关联对象,让分类能正常使用成员变量,有点类似于前面说到的用字典去保存的原理。通过查看源码,我们可以知道AssociationsManager类有一个AssociationsHashMap属性,这个属性是相当于一个字典,用来存储对象-关联对象的,也就是它的key是我们关联对象时传的self,也就是这个Person分类,以这个为key,然后value是一个ObjectAssociationMap,ObjectAssociationMap对象也相当于是一个字典,这个字典的key是我们关联对象时传进去的那个key,value是ObjcAssociation,ObjcAssociation对象里面有两个属性_value和_policy,这两个就是我们关联对象的值和关联策略了。

    简单说AssociationsHashMap存储的是项目中所有分类的关联对象,里面应该是长这样{Person分类: AssociationsHashMap,Student分类: AssociationsHashMap},我们项目中有几个分类有关联对象,那AssociationsHashMap里面就有多少个元素。而AssociationsHashMap里面存放的就是关联对象的key和ObjcAssociation,里面应该长这样{@selector(weight):@(weight),@selector(name):name},一个分类里面有多少个关联对象AssociationsHashMap里面就有多少个元素。最后ObjcAssociation就是保存着关联对象的值和关联策略了。ObjcAssociation{unitptr_t _policy= OBJC_ASSOCIATION_RETAIN_NONATOMIC; id _value=@(weight)}。

    person对象如果被销毁了,那对应的map会被移除
    以下图很好的展示了其中的原理。

    AssociationsHashMap内部

    本文部分内容来自链接:https://www.jianshu.com/p/841a02ca8468

    相关文章

      网友评论

        本文标题:OC基础-category(4)关联对象添加成员变量

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