关联对象
使用runtime为Category动态关联对象
使用 runtime
给系统的类添加属性,首先需要了解对象与属性的关系。我们通过之前的学习知道,对象一开始初始化的时候其属性为nil,给属性赋值其实就是让属性指向一块存储内容的内存,使这个对象的属性跟这块内存产生一种关联。那么如果想动态的添加属性,其实就是动态的产生某种关联就好了。而想要给系统的类添加属性,只能通过分类。
这里给NSObject添加name属性,创建NSObject的分类,我们可以使用@property给分类添加属性。
1 | @property(nonatomic,strong)NSString *name; |
1 | - (void)setName:(NSString *)name { |
动态添加属性
1 | /** |
key值只要是一个指针即可,我们可以传入@selector(name)
获得属性
1 | /** |
移除所有关联对象
1 | - (void)removeAssociatedObjects { |
此时已经成功给NSObject添加name属性,并且NSObject对象可以通过点语法为属性赋值。
关联对象实现原理
实现关联对象技术的核心对象有
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
其中Map同我们平时使用的字典类似。通过key-value一一对应存值。对关联对象技术的核心对象有了一个大概的意识,我们通过源码来探寻这些对象的存在形式以及其作用。
objc_setAssociatedObject
首先找到 objc_setAssociatedObject
函数
1 | void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { |
其实内部调用的是 _object_set_associative_reference
函数,我们在看 _object_set_associative_reference
函数
_object_set_associative_reference
1 | void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { |
_object_set_associative_reference
函数内部我们可以全部找到我们上面说过的实现关联对象技术的核心对象,接下来我们来一个一个看其内部实现原理探寻他们之间的关系。
AssociationsManager
通过AssociationsManager内部源码发现,AssociationsManager内部有一个AssociationsHashMap对象。
1 | class AssociationsManager { |
AssociationsHashMap
来看一下 AssociationsHashMap
内部的源码。
1 |
|
通过 AssociationsHashMap
内部源码我们发现 AssociationsHashMap
继承自 unordered_map
首先来看一下 unordered_map
的源码
1 | template <class _Key, class _Tp, class _Hash = hash<_Key>, class _Pred = equal_to<_Key>, |
从 unordered_map
源码中我们可以看出 _Key
和 _Tp
也就是前两个参数对应着 map
中的 Key
和 Value
,那么对照上面AssociationsHashMap
内源码发现 _Key
中传入的是 disguised_ptr_t
,_Tp
中传入的值则为 ObjectAssociationMap
。
紧接着我们来到 ObjectAssociationMap
中,上述代码中 ObjectAssociationMap
已经标记出,我们发现 ObjectAssociationMap
中同样以 key
、Value
的方式存储着 ObjcAssociation
。
下面来看 ObjcAssociation
1 | class ObjcAssociation { |
我们发现 ObjcAssociation
存储着 _policy
、_value
,而这两个值我们可以发现正是我们调用 objc_setAssociatedObject
函数传入的值,也就是说我们在调用 objc_setAssociatedObject
函数中传入的 value
和 policy
这两个值最终是存储在ObjcAssociation
中的。
现在我们已经对 AssociationsManager
、 AssociationsHashMap
、 ObjectAssociationMap
、ObjcAssociation
四个对象之间的关系有了简单的认识,那么接下来我们来细读源码,看一下 objc_setAssociatedObject
函数中传入的四个参数分别放在哪个对象中充当什么作用。
1 | void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { |
细读上述源码我们可以发现,首先根据我们传入的 value
经过 acquireValue
函数处理获取 new_value
。acquireValue函数内部其实是通过对策略的判断返回不同的值。
1 | static id acquireValue(id value, uintptr_t policy) { |
创建 AssociationsManager
manager,以及拿到 manager
内部的 AssociationsHashMap
即 associations
。
之后我们看到了我们传入的第一个参数 object
,object
经过 DISGUISE
函数被转化为了 disguised_ptr_t
类型的 disguised_object
。
1 | typedef uintptr_t disguised_ptr_t; |
DISGUISE
函数其实仅仅对object做了位运算
之后我们看到被处理成 new_value
的 value
,同 policy
被存入了 ObjcAssociation
中。
而 ObjcAssociation
对应我们传入的 key
被存入了 ObjectAssociationMap
中。disguised_object
和 ObjectAssociationMap
则以 key-value
的形式对应存储在 associations
中也就是 AssociationsHashMap
中。
1 | // create the new association (first time). |
如果我们 value
设置为 nil
的话那么会执行下面的代码
1 | // setting the association to nil breaks the association. |
从上述代码中可以看出,如果我们设置 value
为 nil
时,就会将关联对象从 ObjectAssociationMap
中移除。
通过上图得出,一个实例对象就对应一个 ObjectAssociationMap
,而 ObjectAssociationMap
中存储着多个此实例对象的关联对象的 key
以及 ObjcAssociation
,为 ObjcAssociation
中存储着关联对象的 value
和 policy
策略。
由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的 map
用来存放每一个对象及其对应关联属性表格。
objc_getAssociatedObject
objc_getAssociatedObject
内部调用的是 _object_get_associative_reference
1 | id objc_getAssociatedObject(id object, const void *key) { |
_object_get_associative_reference
1 | id _object_get_associative_reference(id object, void *key) { |
从 _object_get_associative_reference
函数内部可以看出,向 set
方法中那样,反向将 value
一层一层取出最后 return
出去。
objc_removeAssociatedObjects
objc_removeAssociatedObjects
用来删除所有的关联对象,objc_removeAssociatedObjects
函数内部调用的是_object_remove_assocations
函数
1 | void objc_removeAssociatedObjects(id object) |
_object_remove_assocations
1 | void _object_remove_assocations(id object) { |
上述源码可以看出 _object_remove_assocations
函数将 object
对象向对应的所有关联对象全部删除。
总结
关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个 AssociationsManager
中,如果设置关联对象为 nil
,就相当于是移除关联对象。此时我们我们在回过头来看 objc_AssociationPolicy
属性的保存策略。
1 | typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { |
我们会发现其中只有 RETAIN
和 COPY
而为什么没有 weak
呢?
总过上面对源码的分析我们知道,object
经过 DISGUISE
函数被转化为了 disguised_ptr_t
类型的 disguised_object
。
1 | disguised_ptr_t disguised_object = DISGUISE(object); |
而同时我们知道, weak
修饰的属性,当没有拥有对象之后就会被销毁,并且指针置位 nil
,那么在对象销毁之后,虽然在 map
中既然存在值 object
对应的 AssociationsHashMap
,但是因为 object
地址已经被置位 nil
,会造成坏地址访问而无法根据 object
对象的地址转化为 disguised_object
了。