在类中,我们使用@property (nonatomic, copy) NSString *name
生成一个属性。它干了三件事情:
- 声明一个
_name
的变量; - 声明
setName:
和getName
方法; - setter和getter方法的默认实现;
但是在分类中写上述这样一个属性的,它只有setter和getter方法的声明,并不会生成成员变量和实现setter和getter方法,因此如果想要在分类中实现属性的话得使用关联对象的方式。
关联对象的使用
首先我们要明白为什么要使用关联对象?
在分类中@property
并不会自动生成实例变量以及存取方法,另外在分类是不能声明成员变量的。从源码的角度去看,Category在编译时期生成的结构体中根本没有存放成员变量的数组。基于上面的原因,如果我们要实现类中属性那样的效果,就要使用关联对象。
关联对象的应用如下:
1 | // .h |
关联对象的实现
这里使用objc4-750.1
的源代码,你可以通过这里下载。我们常用的关于关联对象的API主要有以下几个:
1 | void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); |
这三个方法的作用分别是:
- 以键值对形式添加关联对象
- 根据key获取关联对象
- 移除所有关联对象
关联对象的核心对象
在分析关联对象的API实现之前,先看一下关联对象的核心对象。
AssociationsManager
AssociationsManager
的定义如下:
1 | spinlock_t AssociationsManagerLock; |
AssociationsManager
会维护一个AssociationsHashMap
,在初始化的时候,调用AssociationsManagerLock.lock()
,在析构时会调用AssociationsManagerLock.unlock()
,而associations
用于取得一个全局的AssociationsHashMap
。
另外AssociationsManager
通过一个自旋锁spinlock_t AssociationsManagerLock
来确保对AssociationsHashMap
的操作是线程安全的。
AssociationsHashMap
HashMap
相当于OC中的NSDictionary
。AssociationsHashMap
的定义如下:
1 | class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> { |
AssociationsHashMap
继承自unordered_map
,使用的是C++语法。它的作用就是保存从对象的disguised_ptr_t
到ObjectAssociationMap
的映射。 我们可以理解为AssociationsHashMap以key-value的形式存着若干个ObjectAssociationMap。
ObjectAssociationMap
ObjectAssociationMap
的定义如下:
1 | class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> { |
ObjectAssociationMap
保存了从key
到ObjcAssociation
的映射,我们可以理解为ObjectAssociationMap以key-value的形式存着若干个ObjcAssociation对象。这个数据结构保存了当前对象对应的所有关联对象:
ObjcAssociation
ObjcAssociation
的定义如下:
1 | class ObjcAssociation { |
ObjcAssociation
对象保存了对象对应的关联对象,其中的_policy
和_value
字段存的便是我们使用objc_setAssociatedObject
方法时传入的policy
和value
。
objc_setAssociatedObject
通过objc_setAssociatedObject
函数,我们添加一个关联对象,其实现如下:
1 | void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { |
接着看一下_object_set_associative_reference
的实现:
1 | void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { |
通过上面的源码以及注释可以知道objc_setAssociatedObject
的流程,接着我们用一张图片来说明关联对象的原理:
objc_getAssociatedObject
在理解了objc_setAssociatedObject
的实现之后,objc_getAssociatedObject
就变得容易理解了,其实现如下:
1 | id objc_getAssociatedObject(id object, const void *key) { |
接着看一下_object_get_associative_reference
的实现:
1 | id _object_get_associative_reference(id object, void *key) { |
objc_removeAssociatedObjects
objc_removeAssociatedObjects
用来删除所有的关联对象,其实现:
1 | void objc_removeAssociatedObjects(id object) |
通过hasAssociatedObjects
函数判断是否含有关联对象,如果有则调用_object_remove_assocations
。
在objc_setAssociatedObject
的实现中有提到在添加关联对象的时候如果ObjectAssociationMap
不存在,则会初始化一个ObjectAssociationMap
,再实例化ObjcAssociation
对象添加到Map中,并调用 setHasAssociatedObjects
函数。setHasAssociatedObjects
函数用来将isa结构体中的has_assoc
标记为true
,而hasAssociatedObjects
函数则用来获取该该标志位的结果。
接着看一下_object_remove_assocations
,其实现如下:
1 | void _object_remove_assocations(id object) { |
_object_remove_assocations
会将对象包含的所有关联对象加入到一个vector
中,删除AssociationsHashMap
中对应的节点,然后对所有的 ObjcAssociation
对象调用 ReleaseValue()
,释放不再被需要的值。
总结
关联对象的实现
- 关联对象的本质就是
ObjcAssociation
对象; ObjectAssociationMap
以key
为健存储关联对象的数据结构(ObjcAssociation
对象);- 每一个对象都对应着一个
ObjectAssociationMap
,对象中的has_assoc
用来确定是否含有关联对象,而对象与ObjectAssociationMap
之间的映射关系则存储在AssociationsHashMap
中; AssociationsHashMap
是全局唯一的,有AssociationsManager
管理。
分类中能否实现属性
如果将属性看成是实例变量,那答案是不能,如果将属性看成是存取方法以及存储值的集合,那么分类是可以实现属性的,个人更倾向于前者。
weak类型的关联对象
关联对象里是没有weak类型的策略,而在开发过程中,真的几乎没有说要用弱类型的关联对象,除非是为了用而用。我是这么理解的,既然叫做关联对象,那肯定需要和自身生命周期有联系才谈得上关联,使用weak则代表对象和自身生命周期是没有联系,自身的释放不会影响关联对象。综上我认为weak类型的关联对象是没有意义的。
但是如果非要实现一个weak类型的关联对象也不是不可以,拿个中间对象包装一下即可。代码如下:
1 | #pragma mark - Weak Associated Object |