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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容