博客链接 从源码理解关联属性
在类中,我们使用@property (nonatomic, copy) NSString *name
生成一个属性。它干了三件事情:
- 声明一个
_name
的变量; - 声明
setName:
和getName
方法; - setter和getter方法的默认实现;
但是在分类中写上述这样一个属性的,它只有setter和getter方法的声明,并不会生成成员变量和实现setter和getter方法,因此如果想要在分类中实现属性的话得使用关联对象的方式。
关联对象的使用
首先我们要明白为什么要使用关联对象?
在分类中@property
并不会自动生成实例变量以及存取方法,另外在分类是不能声明成员变量的。从源码的角度去看,Category在编译时期生成的结构体中根本没有存放成员变量的数组。基于上面的原因,如果我们要实现类中属性那样的效果,就要使用关联对象。
关联对象的应用如下:
// .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主要有以下几个:
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
的定义如下:
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中的NSDictionary
。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); }
};
AssociationsHashMap
继承自unordered_map
,使用的是C++语法。它的作用就是保存从对象的disguised_ptr_t
到ObjectAssociationMap
的映射。 我们可以理解为AssociationsHashMap以key-value的形式存着若干个ObjectAssociationMap。
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); }
};
ObjectAssociationMap
保存了从key
到ObjcAssociation
的映射,我们可以理解为ObjectAssociationMap以key-value的形式存着若干个ObjcAssociation对象。这个数据结构保存了当前对象对应的所有关联对象:
ObjcAssociation
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; }
};
ObjcAssociation
对象保存了对象对应的关联对象,其中的_policy
和_value
字段存的便是我们使用objc_setAssociatedObject
方法时传入的policy
和value
。
objc_setAssociatedObject
通过objc_setAssociatedObject
函数,我们添加一个关联对象,其实现如下:
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
的实现:
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
就变得容易理解了,其实现如下:
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
接着看一下_object_get_associative_reference
的实现:
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
用来删除所有的关联对象,其实现:
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
,其实现如下:
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
对象; -
ObjectAssociationMap
以key
为健存储关联对象的数据结构(ObjcAssociation
对象); - 每一个对象都对应着一个
ObjectAssociationMap
,对象中的has_assoc
用来确定是否含有关联对象,而对象与ObjectAssociationMap
之间的映射关系则存储在AssociationsHashMap
中; -
AssociationsHashMap
是全局唯一的,有AssociationsManager
管理。
分类中能否实现属性
如果将属性看成是实例变量,那答案是不能,如果将属性看成是存取方法以及存储值的集合,那么分类是可以实现属性的,个人更倾向于前者。
weak类型的关联对象
关联对象里是没有weak类型的策略,而在开发过程中,真的几乎没有说要用弱类型的关联对象,除非是为了用而用。我是这么理解的,既然叫做关联对象,那肯定需要和自身生命周期有联系才谈得上关联,使用weak则代表对象和自身生命周期是没有联系,自身的释放不会影响关联对象。综上我认为weak类型的关联对象是没有意义的。
但是如果非要实现一个weak类型的关联对象也不是不可以,拿个中间对象包装一下即可。代码如下:
#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;
}