介绍 对象关联,以及如何为Category添加weak属性
apple open source
对象关联原理
创建一个类的分类Category
,并添加一个属性CategoryProperty
,分类中的@CategoryProperty并没有帮我们生成实例变量以及存取方法,要求我们手动实现,这时候就需要用到对象关联。
在分类中,因为类的实例变量的布局已经固定,使用 @property 已经无法向固定的布局中添加新的实例变量(这样做可能会覆盖子类的实例变量),所以我们需要使用关联对象以及两个方法来模拟构成属性的三个要素。
- 关联对象其实就是 ObjcAssociation 对象
- 关联对象由 AssociationsManager 管理并在 AssociationsHashMap 存储
- 对象的指针以及其对应 ObjectAssociationMap 以键值对的形式存储在 AssociationsHashMap 中
ObjectAssociationMap 则是用于存储关联对象的数据结构 - 每一个对象都有一个标记位 has_assoc 指示对象是否含有关联对象
对象关联会用到以下三个方法:
1 | //以键值对形式添加关联对象 |
objc_removeAssociatedObjects 函数我们一般是用不上的,因为这个函数会移除一个对象的所有关联对象,将该对象恢复成“原始”状态。这样做就很有可能把别人添加的关联对象也一并移除,这并不是我们所希望的。所以一般的做法是通过给 objc_setAssociatedObject 函数传入 nil 来移除某个已有的关联对象。
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) { |
其中设置关联对象的逻辑:
- 使用 old_association(0, nil) 创建一个临时的 ObjcAssociation 对象(用于持有原有的关联对象,方便在方法调用的最后释放值)
- 调用 acquireValue 对 new_value 进行 retain 或者 copy
- 初始化一个 AssociationsManager,并获取唯一的保存关联对象的哈希表 AssociationsHashMap
- 先使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap
- 如果没有找到,初始化一个 ObjectAssociationMap,再实例化 ObjcAssociation 对象添加到 Map 中,并调用 setHasAssociatedObjects 方法,表明当前对象含有关联对象
- 如果找到了对应的 ObjectAssociationMap,就要看 key 是否存在了,由此来决定是更新原有的关联对象,还是增加一个
- 最后,如果原来的关联对象有值的话,会调用 ReleaseValue() 释放关联对象的值
objc_getAssociatedObject
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) { |
其中寻找关联对象的逻辑:
- 获取静态变量 AssociationsHashMap
- 以 DISGUISE(object) 为 key 查找 AssociationsHashMap
- 以 void *key 为 key 查找 ObjcAssociation
- 根据 policy 调用相应的方法
- 返回关联对象 ObjcAssociation 的值
objc_removeAssociatedObjects
objc_removeAssociatedObjects
的调用:
1 | void objc_removeAssociatedObjects(id object) |
为了加速移除对象的关联对象的速度,我们会通过标记位 has_assoc 来避免不必要的方法调用,在确认了对象和关联对象的存在之后,才会调用 _object_remove_assocations 方法移除对象上所有的关联对象,方法会将对象包含的所有关联对象加入到一个 vector 中,然后对所有的 ObjcAssociation 对象调用 ReleaseValue() 方法,释放不再被需要的值:
1 | void _object_remove_assocations(id object) { |
AssociationsManager
AssociationsManager 在源代码中的定义是这样的:
1 | class AssociationsManager { |
AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。
它维护了 spinlock_t 和 AssociationsHashMap 的单例,初始化它的时候会调用 lock.lock() 方法,在析构时会调用 lock.unlock(),而 associations 方法用于取得一个全局的 AssociationsHashMap 单例。
也就是说 AssociationsManager 通过持有一个自旋锁 spinlock_t 保证对 AssociationsHashMap 的操作是线程安全的,即每次只会有一个线程对 AssociationsHashMap 进行操作。
AssociationsHashMap ObjectAssociationMap
AssociationsHashMap 用与保存从对象的 disguised_ptr_t 到 ObjectAssociationMap 的映射:
1 | class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> { |
ObjectAssociationMap 则保存了从 key 到关联对象 ObjcAssociation 的映射,这个数据结构保存了当前对象对应的所有关联对象:
1 | class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> { |
ObjcAssociation
ObjcAssociation 就是真正的关联对象的类,上面的所有数据结构只是为了更好的存储它。ObjcAssociation 包含了 policy 以及 value:
1 | class ObjcAssociation { |
为category添加weak属性
objc_AssociationPolicy
并没有提供一个类似OBJC_ASSOCIATION_WEAK_NONATOMIC的的东西,那么就需要自己在需要的时候手动将属性设置为nil。
要实现 weak ,说白了就是要做到两点:1、引用计数器不变;2、对象销毁后自动设置为 nil。而在 runtime 所提供的枚举中,OBJC_ASSOCIATION_ASSIGN 就已经做到了第一点,我们只需要实现第二点即可。第二点是要在对象销毁后,将 weak 引用设置为 nil ,所以我们要捕获这个对象销毁的时机,或者接收这个对象销毁的事件。在 ARC 中,对象销毁时机其实就是 dealloc 方法调用的时机,我们可以在这个方法里将这个 weak 引用设置为 nil
以下为解决思路:
1 | // .h |
Category里:
1 | // NSObject+property.h |