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、如果被关联对象销毁,对应的关联属性也都会销毁
