从源码理解关联对象

在类中,我们使用@property (nonatomic, copy) NSString *name生成一个属性。它干了三件事情:

  1. 声明一个_name的变量;
  2. 声明setName:getName方法;
  3. setter和getter方法的默认实现;

但是在分类中写上述这样一个属性的,它只有setter和getter方法的声明,并不会生成成员变量和实现setter和getter方法,因此如果想要在分类中实现属性的话得使用关联对象的方式。

关联对象的使用

首先我们要明白为什么要使用关联对象
在分类中@property并不会自动生成实例变量以及存取方法,另外在分类是不能声明成员变量的。从源码的角度去看,Category在编译时期生成的结构体中根本没有存放成员变量的数组。基于上面的原因,如果我们要实现类中属性那样的效果,就要使用关联对象。

关联对象的应用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// .h
@interface FatherA : NSObject

@property (nonatomic, copy) NSString *name;

@end

// .m
@implementation FatherA

- (void)setName:(NSString *)name {
objc_setAssociatedObject(self,
@selector(name),
name,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
return objc_getAssociatedObject(self, _cmd);
}

@end

关联对象的实现

这里使用objc4-750.1的源代码,你可以通过这里下载。我们常用的关于关联对象的API主要有以下几个:

1
2
3
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);

这三个方法的作用分别是:

  • 以键值对形式添加关联对象
  • 根据key获取关联对象
  • 移除所有关联对象

关联对象的核心对象

在分析关联对象的API实现之前,先看一下关联对象的核心对象。

AssociationsManager

AssociationsManager的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spinlock_t AssociationsManagerLock;

class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }

AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};

AssociationsHashMap *AssociationsManager::_map = NULL;

AssociationsManager会维护一个AssociationsHashMap,在初始化的时候,调用AssociationsManagerLock.lock(),在析构时会调用AssociationsManagerLock.unlock(),而associations用于取得一个全局的AssociationsHashMap

另外AssociationsManager通过一个自旋锁spinlock_t AssociationsManagerLock来确保对AssociationsHashMap的操作是线程安全的。

AssociationsHashMap

HashMap相当于OC中的NSDictionaryAssociationsHashMap的定义如下:

1
2
3
4
5
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); }
};

AssociationsHashMap继承自unordered_map,使用的是C++语法。它的作用就是保存从对象的disguised_ptr_tObjectAssociationMap的映射。 我们可以理解为AssociationsHashMap以key-value的形式存着若干个ObjectAssociationMap

ObjectAssociationMap

ObjectAssociationMap的定义如下:

1
2
3
4
5
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); }
};

ObjectAssociationMap保存了从keyObjcAssociation的映射,我们可以理解为ObjectAssociationMap以key-value的形式存着若干个ObjcAssociation对象。这个数据结构保存了当前对象对应的所有关联对象:

ObjcAssociation

ObjcAssociation的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
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; }
};

ObjcAssociation对象保存了对象对应的关联对象,其中的_policy_value字段存的便是我们使用objc_setAssociatedObject方法时传入的policyvalue

objc_setAssociatedObject

通过objc_setAssociatedObject函数,我们添加一个关联对象,其实现如下:

1
2
3
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}

接着看一下_object_set_associative_reference的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
// 创建一个ObjcAssociation局部变量,持有原有的关联对象和最后的释放
ObjcAssociation old_association(0, nil);
// 调用acquireValue对new_value进行retain或者copy
id new_value = value ? acquireValue(value, policy) : nil;
{
// 初始化一个AssociationsManager,并获取AssociationsHashMap
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// disguised_ptr_t是AssociationsHashMap中的key,通过传入的object得到
disguised_ptr_t disguised_object = DISGUISE(object);

// new_value有值代表设置或者更新关联对象的值,否则表示删除一个关联对象
if (new_value) {
// break any existing association.
// 查找ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
// ObjectAssociationMap存在
// 判断key是否存在,key存在更新原有的关联对象,key不存在,则新增,并且新增的位置需要结合end()
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不存在
// 初始化一个ObjectAssociationMap,再实例化ObjcAssociation对象添加到Map中,并调用 setHasAssociatedObjects函数
// setHasAssociatedObjects标明当前类具有关联类
// 它会将isa结构体中的has_assoc标记为true
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.
// 查找ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// ObjectAssociationMap存在
// 判断key是否存在,key存在则调用erase函数来删除ObjectAssociationMap中key对应的节点
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).
// 原来的关联对象有值,调用ReleaseValue函数释放关联对象的值
if (old_association.hasValue()) ReleaseValue()(old_association);
}

通过上面的源码以及注释可以知道objc_setAssociatedObject的流程,接着我们用一张图片来说明关联对象的原理:
关联对象原理

objc_getAssociatedObject

在理解了objc_setAssociatedObject的实现之后,objc_getAssociatedObject就变得容易理解了,其实现如下:

1
2
3
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}

接着看一下_object_get_associative_reference的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 初始化一个AssociationsManager,并获取AssociationsHashMap
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 通过object获得disguised_ptr_t,用作在AssociationsHashMap的key
disguised_ptr_t disguised_object = DISGUISE(object);
// 查找ObjectAssociationMap的位置
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
// 查找ObjcAssociation对象
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
// 说明是强类型,retain操作
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
// autorelease
objc_autorelease(value);
}
return value;
}

objc_removeAssociatedObjects

objc_removeAssociatedObjects用来删除所有的关联对象,其实现:

1
2
3
4
5
6
void objc_removeAssociatedObjects(id object) 
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}

通过hasAssociatedObjects函数判断是否含有关联对象,如果有则调用_object_remove_assocations

objc_setAssociatedObject的实现中有提到在添加关联对象的时候如果ObjectAssociationMap不存在,则会初始化一个ObjectAssociationMap,再实例化ObjcAssociation对象添加到Map中,并调用 setHasAssociatedObjects函数。setHasAssociatedObjects函数用来将isa结构体中的has_assoc标记为true,而hasAssociatedObjects函数则用来获取该该标志位的结果。

接着看一下_object_remove_assocations,其实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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());
}

_object_remove_assocations会将对象包含的所有关联对象加入到一个vector中,删除AssociationsHashMap中对应的节点,然后对所有的 ObjcAssociation对象调用 ReleaseValue(),释放不再被需要的值。

总结

关联对象的实现

  • 关联对象的本质就是ObjcAssociation对象;
  • ObjectAssociationMapkey为健存储关联对象的数据结构(ObjcAssociation对象);
  • 每一个对象都对应着一个ObjectAssociationMap,对象中的has_assoc用来确定是否含有关联对象,而对象与ObjectAssociationMap之间的映射关系则存储在AssociationsHashMap中;
  • AssociationsHashMap是全局唯一的,有AssociationsManager管理。

分类中能否实现属性

如果将属性看成是实例变量,那答案是不能,如果将属性看成是存取方法以及存储值的集合,那么分类是可以实现属性的,个人更倾向于前者。

weak类型的关联对象

关联对象里是没有weak类型的策略,而在开发过程中,真的几乎没有说要用弱类型的关联对象,除非是为了用而用。我是这么理解的,既然叫做关联对象,那肯定需要和自身生命周期有联系才谈得上关联,使用weak则代表对象和自身生命周期是没有联系,自身的释放不会影响关联对象。综上我认为weak类型的关联对象是没有意义的。

但是如果非要实现一个weak类型的关联对象也不是不可以,拿个中间对象包装一下即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#pragma mark - Weak Associated Object

@interface _NNWeakAssociatedWrapper : NSObject

@property (nonatomic, weak) id associatedObject;

@end

@implementation _NNWeakAssociatedWrapper

@end

void nn_objc_setWeakAssociatedObject(id object, const void * key, id value) {
_NNWeakAssociatedWrapper *wrapper = objc_getAssociatedObject(object, key);
if (!wrapper) {
wrapper = [_NNWeakAssociatedWrapper new];
objc_setAssociatedObject(object, key, wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
wrapper.associatedObject = value;
}

id nn_objc_getWeakAssociatedObject(id object, const void * key) {
id wrapper = objc_getAssociatedObject(object, key);

id objc = wrapper && [wrapper isKindOfClass:_NNWeakAssociatedWrapper.class] ?
[(_NNWeakAssociatedWrapper *)wrapper associatedObject] :
nil;

return objc;
}