「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战」。
方法三:利用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);
//打印输出
2022-02-21 12:06:33.275907+0800 CategoryAssociate[42088:1396908] p3.name = Harden,age = 13,height = 196
2022-02-21 12:06:33.276356+0800 CategoryAssociate[42088:1396908] p2.name = Durant,age = 7,height = 211
2022-02-21 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会被移除
以下图很好的展示了其中的原理。