1、能否在分类中添加成员变量
如果在分类中直接添加成员变量
@interface Person (Test1) {
int _weight;
}
系统会报错:
!Instance variables may not be placed in categories (实例变量不能放在类别中)
说明分类中不能直接添加成员变量。
如果在分类中添加属性:
@interface Person (Test1)
@property (nonatomic, assign) int weight;
@end
当调用set或者get方法时,控制台会报错:
~:-[Person setWeight:]: unrecognized selector sent to instance 0x1007160e0
~:-[Person weight]: unrecognized selector sent to instance 0x105011920
说明分类可以添加属性,但是添加属性后,只会自动添加set和get方法声明,不会实现set和get方法。
2、利用字典给分类添加成员变量
声明一个全局字典,用于保存属性
NSMutableDictionary *dic_;
+ (void)load {
dic_ = [[NSMutableDictionary alloc] init];
}
- (void)setWeight:(int)weight {
NSString *key = [NSString stringWithFormat:@"%p",self];
dic_[key] = @(weight);
}
- (int)weight {
NSString *key = [NSString stringWithFormat:@"%p",self];
return [dic_[key] intValue];
}
通过分类中的字典可以保存分类里添加属性的值,但是有以下几个缺点
1、内存泄漏,字典是全局变量,会一直保存在内存中。
2、线程安全,不同的对象在不同的线程同时访问分类中的成员变量,可能会导致读写错误。
3、代码量比较大,实现起来比较麻烦。
3、关联对象
3.1、基本用法
关联对象主要方法有:
// 添加关联对象
objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy);
// 读取关联对象
objc_getAssociatedObject(id object, const void * key);
// 移除所有关联对象
objc_removeAssociatedObjects(id _Nonnull object);
关联策略对应关系:
代码举例:
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @"key", name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name {
return objc_getAssociatedObject(self, "key");
}
3.2、key的用法
3.2.1、使用变量地址作为key
static const void *NameKey = &NameKey;
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name {
return objc_getAssociatedObject(self, NameKey);
}
3.2.2、使用char类型变量地址作为key
static const char NameKey;
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, &NameKey, name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name {
return objc_getAssociatedObject(self, &NameKey);
}
3.2.3、使用字符串作为key
#define NameKey @"name"
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name {
return objc_getAssociatedObject(self, NameKey);
}
3.2.4、使用方法地址作为key(推荐)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name {
return objc_getAssociatedObject(self, _cmd);
}
优点:可读性较高、如果方法名写错系统会有提示,书写简单。
其中_cmd == @selector(name),OC方法有两个隐式参数,一个是self,一个是_cmd,比如name函数可能是:
- (NSString *)nameSelf:(id)self cmd:(SEL):_cmd {
}
4、关联对象原理
查看 objc源码 objc-references.mm中的_object_set_associative_reference方法和_object_get_associative_reference方法:
_object_get_associative_reference(id object, const void *key)
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy){}
可以看出关联对象过程中主要用到一下几个数据结构:
AssociationsManager、AssociationsHashMap、ObjectAssociationMap、ObjcAssociation
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
class AssociationsManager {
......
AssociationsHashMap &get() {
return _mapStorage.get();
}
......
};
class ObjcAssociation {
uintptr_t _policy;
id _value;
......
}
它们的结构关系如下:
所有实例对象的关联对象都由一个AssociationsManager管理,其内部有一个AssociationsHashMap对象,AssociationsHashMap内部保存着实例对象(object)和存储着实例对象对应自己关联对象的ObjectAssociationMap,ObjectAssociationMap内部存储着关联对象的key和对应关联对象的value和策略。
5、总结
1、关联对象并不是存储在被关联对象本身
2、关联对象存储在全局统一的一个AssociationsManager中
3、移除关联对象将关联对象设置nil即可(通过查看源码可知,当value==nil时会调用erase方法)
4、移除所有关联对象,调用objc_removeAssociatedObjects(id _Nonnull object)
5、如果被关联对象销毁,对应的关联属性也都会销毁