关联对象
一、类添加成员变量与分类添加成员变量的区别
(一)类
// 定义 一个 MyPerson 类
@interface MyPerson : NSObject
@property (nonatomic, assign) int age;
@end
@implementation MJPerson
@end
在 MJPerson 类中,使用 @property (nonatomic, assign) int age
创建一个变量的话, 系统会默认为我们做三件事
1、生成 _name 成员变量
{
int _age;
}
2、生成 get/set 方法的声明
- (void)setAge:(int)age;
- (int)age;
3、生成 get/set 方法的实现
- (void)setAge:(int)age {
_age = age;
}
- (int)age {
return _age;
}
(二)分类
// 定义 一个 MyPerson+Test 分类
@interface MyPerson (Test)
@property (nonatomic, assign) int weight;
@end
@implementation MyPerson (Test)
@end
在 MyPerson+Test 这个分类中,使用@property (nonatomic, assign) int weight;
创建一个变量 weight ,系统只会为我们做一件事情。
1、只会生成 get/set 方法的声明,不会生成 get/set 方法的实现。
- (void)setWeight:(int)weight;
- (int)weight;
那么,我们自己实现 成员变量的 getter和setter 方法呢?

上面的代码可以看出,直接报错,提示说,实例变量不能放在分类里面。
(三)手动实现 分类的 setter 和 getter 方法
@interface MyPerson (Test)
@property (nonatomic, assign) int weight;
@end
@implementation MyPerson (Test)
- (void)setWeight:(int)weight {
}
- (int)weight {
return 0;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyPerson *person = [[MyPerson alloc] init];
person.age = 18;
person.weight = 60;
NSLog(@"age is %d, weight is %d", person.age, person.weight);
}
return 0;
}
运行上面的代码,打印结果为age is 18, weight is 0
,为什么我们赋值给 weight = 60 这个不好使,但是 age =18 就能有效果呢?
person.age = 18;
这句代码,相当于把 18 赋值给了_age 这个成员变量。
person.age
就是去getAge 直接返回了 _age 这个变量的值。
person.weight = 60
这句代码,可以看到分类的实现里面,并没有保存 _weight 这个成员变量,
person.weight
,分类代码里写死的 返回 0 ,所以直接返回结果 0
二、保存分类的成员变量的值
(一)使用全局变量保存
对于上面的 demo ,不能保存 _weight 的值,我们试想下,可以单独写一个全局变量来保存外面传进来的值
int weight_;
@implementation MyPerson (Test)
- (void)setWeight:(int)weight {
weight_ = weight;
}
- (int)weight {
return weight_;
}
@end
运行上面的代码,的确能做到外面的值保存下来,但是存在问题,相当于一个 全局的变量,创建多个对象,会产生多个对象公用一个 weight 变量。
MyPerson *person = [[MyPerson alloc] init];
person.age = 18;
person.weight = 60;
MyPerson *person1 = [[MyPerson alloc] init];
person1.age = 23;
person1.weight = 100;
// age is 18, weight is 100
NSLog(@"age is %d, weight is %d", person.age, person.weight);
// age is 23, weight is 100
NSLog(@"age is %d, weight is %d", person1.age, person1.weight);
(二)使用dictionary 保存
NSMutableDictionary *weightDic_;
@implementation MyPerson (Test)
+ (void)load {
weightDic_ = [NSMutableDictionary dictionary];
}
- (void)setWeight:(int)weight {
NSString *key = [NSString stringWithFormat:@"%p", self];
weightDic_[key] = @(weight);
}
- (int)weight {
NSString *key = [NSString stringWithFormat:@"%p", self];
return [weightDic_[key] intValue];
}
@end
运行上面的代码打印结果:
2020-12-21 23:31:44.820168+0800 MyTestDemo[29582:5762922] age is 18, weight is 60
2020-12-21 23:31:44.820785+0800 MyTestDemo[29582:5762922] age is 23, weight is 100
看到打印结果给人的感觉像是和系统生成的实现方法一样,但是内部却大有不同
- 1、存储内存不同
person1.age = 23;
23 是存储在 person1 对象内部。
person1.weight = 100;
100是存放正在全局的字典对象里面。 - 2、安全问题
- 3、如果分类在创建一个变量,那么就会在创建一个全局字典,代码冗余。
(三)使用关联对象方法
主要使用 Runtime 的 API ,来修改 成员变量 name 的值。
@interface MyPerson (Test)
@property (nonatomic, copy) NSString *name
@end
#import <objc/runtime.h>
@implementation MyPerson (Test)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, @selector(name));
}
@end
MyPerson *person = [[MyPerson alloc] init];
person.age = 18;
person.name = @"tom";
MyPerson *person1 = [[MyPerson alloc] init];
person1.age = 23;
person1.name = @"yang";
NSLog(@"age is %d, name is %@", person.age, person.name);
NSLog(@"age is %d, name is %@", person1.age, person1.name);
打印结果为:
2020-12-21 23:55:49.461183+0800 MyTestDemo[29679:5770200] age is 18, name is tom
2020-12-21 23:55:49.461634+0800 MyTestDemo[29679:5770200] age is 23, name is yang
三、管理对象剖析
(一)为什么要使用关联对象
在上面的例子中可以有多种方法实现:
// 1、使用 static const void * 作为key
static const void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
// 2、使用 static char 作为key
static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
// 3、直接使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");
// 4、使用get方法的@selecor作为key,或者在get方法中使用_cmd,objc_getAssociatedObject(obj,_cmd);
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
上面的几种方法,我们使用最多和最方便的就是第4种方案。
(二)关联对象 API
1、添加关联对象 objc_setAssociatedObject
底层源码
// objc_setAssociatedObject
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
- object: 关联的对象,一般值当前的类
- value: 需要关联的值
- policy:策略,主要有以下5种策略模式,具体使用见下面表格
-
OBJC_ASSOCIATION_ASSIGN
、 -
OBJC_ASSOCIATION_RETAIN_NONATOMIC
、 -
OBJC_ASSOCIATION_COPY_NONATOMIC
、 -
OBJC_ASSOCIATION_RETAIN
、 OBJC_ASSOCIATION_COPY
-
策略主要对应的使用方法
修饰符 | objc_AssociationPolicy |
---|---|
assign | OBJC_ASSOCIATION_ASSIGN |
strong,nonatomic | OBJC_ASSOCIATION_RETAIN_NONATOMIC |
copy,nonatomic | OBJC_ASSOCIATION_COPY_NONATOMIC |
strong,atomic | OBJC_ASSOCIATION_RETAIN |
copy,atomic | OBJC_ASSOCIATION_COPY |
2.获取关联对象objc_getAssociatedObject
objc_getAssociatedObject(id _Nonnull object_, const void * _Nonnull key)
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
}
return value;
}
3、移除关联对象 objc_removeAssociatedObjects
objc_removeAssociatedObjects(id _Nonnull object)
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
(三) 关联对象的原理
分析上面的源码,得到实现关联对象技术的核心对象有下面几个
1、AssociationsManager
// AssociationsManager
class AssociationsManager {
static spinlock_t _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { _lock.lock(); }
~AssociationsManager() { _lock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
2、AssociationsHashMap
// AssociationsHashMap
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
3、ObjectAssociationMap
// ObjectAssociationMap
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
4、ObjcAssociation
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
uintptr_t policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
这四个函数对应代码的关系如下

思考
思考
:关联对象的成员变量存放在哪里
- 关联对象并不是存储在被关联对象本身内存中
- 关联对象存储在全局的统一一个的 AssociationsManager 中
- 设置关联对象为 nil 相当于删除关联对象