关联对象 使用runtime为Category动态关联对象 使用 runtime 给系统的类添加属性,首先需要了解对象与属性的关系。我们通过之前的学习知道,对象一开始初始化的时候其属性为nil,给属性赋值其实就是让属性指向一块存储内容的内存,使这个对象的属性跟这块内存产生一种关联。那么如果想动态的添加属性,其实就是动态的产生某种关联就好了。而想要给系统的类添加属性,只能通过分类。 这里给NSObject添加name属性,创建NSObject的分类,我们可以使用@property给分类添加属性。
1 @property(nonatomic,strong)NSString *name;
1 2 3 4 5 6 - (void)setName:(NSString *)name { objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)name { return objc_getAssociatedObject(self, @"name"); }
动态添加属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /** @param object 给哪个对象添加属性,这里要给自己添加属性,用self。 @param name 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。 @param value 关联的值,也就是set方法传入的值给属性去保存。 @param policy 策略,属性以什么形式保存。 */ objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, // 指定一个弱引用相关联的对象 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相关的对象被复制,非原子性 OBJC_ASSOCIATION_RETAIN = 01401, // 指定相关对象的强引用,原子性 OBJC_ASSOCIATION_COPY = 01403 // 指定相关的对象被复制,原子性 };
key值只要是一个指针即可,我们可以传入@selector(name)
获得属性 1 2 3 4 5 /** @param object 获取哪个对象里面的关联的属性。 @param key 属性键值,与objc_setAssociatedObject中的key相对应,即通过key值取出value。 */ objc_getAssociatedObject(id object, const void *key);
移除所有关联对象 1 2 3 4 - (void)removeAssociatedObjects { // 移除所有关联对象 objc_removeAssociatedObjects(self); }
此时已经成功给NSObject添加name属性,并且NSObject对象可以通过点语法为属性赋值。
关联对象实现原理 实现关联对象技术的核心对象有
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
其中Map同我们平时使用的字典类似。通过key-value一一对应存值。对关联对象技术的核心对象有了一个大概的意识,我们通过源码来探寻这些对象的存在形式以及其作用。
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 函数,我们在看 _object_set_associative_reference 函数
_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 void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { ObjcAssociation old_association (0 , nil) ; id new_value = value ? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap &associations (manager.associations()) ; disguised_ptr_t disguised_object = DISGUISE(object); if (new_value) { AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.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 { ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); } } else { AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); } } } } if (old_association.hasValue()) ReleaseValue()(old_association); }
_object_set_associative_reference 函数内部我们可以全部找到我们上面说过的实现关联对象技术的核心对象,接下来我们来一个一个看其内部实现原理探寻他们之间的关系。
AssociationsManager 通过AssociationsManager内部源码发现,AssociationsManager内部有一个AssociationsHashMap对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 class AssociationsManager { static AssociationsHashMap *_map; public : AssociationsManager() { AssociationsManagerLock.lock(); } ~AssociationsManager() { AssociationsManagerLock.unlock(); } AssociationsHashMap &associations () { if (_map == NULL ) _map = new AssociationsHashMap(); return *_map; } };
AssociationsHashMap 来看一下 AssociationsHashMap 内部的源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #if TARGET_OS_WIN32 typedef hash_map<void *, ObjcAssociation> ObjectAssociationMap; typedef hash_map<disguised_ptr_t , ObjectAssociationMap *> AssociationsHashMap; #else typedef ObjcAllocator<std ::pair <void * const , ObjcAssociation> > ObjectAssociationMapAllocator; 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); } }; typedef ObjcAllocator<std ::pair <const disguised_ptr_t , ObjectAssociationMap*> > AssociationsHashMapAllocator; 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); } }; #endif
通过 AssociationsHashMap 内部源码我们发现 AssociationsHashMap 继承自 unordered_map 首先来看一下 unordered_map 的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 template <class _Key , class _Tp , class _Hash = hash<_Key>, class _Pred = equal_to<_Key>, class _Alloc = allocator<pair <const _Key, _Tp> > > class _LIBCPP_TEMPLATE_VIS unordered_map { public : typedef _Key key_type; typedef _Tp mapped_type; typedef _Hash hasher; typedef _Pred key_equal; typedef _Alloc allocator_type; typedef pair <const key_type, mapped_type> value_type; typedef pair <key_type, mapped_type> __nc_value_type; typedef value_type& reference; typedef const value_type& const_reference; static_assert ((is_same<value_type, typename allocator_type::value_type>::value), "Invalid allocator::value_type" ); ... }
从 unordered_map 源码中我们可以看出 _Key 和 _Tp 也就是前两个参数对应着 map 中的 Key 和 Value,那么对照上面AssociationsHashMap 内源码发现 _Key 中传入的是 disguised_ptr_t,_Tp 中传入的值则为 ObjectAssociationMap。
紧接着我们来到 ObjectAssociationMap 中,上述代码中 ObjectAssociationMap 已经标记出,我们发现 ObjectAssociationMap 中同样以 key、Value 的方式存储着 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 函数传入的值,也就是说我们在调用 objc_setAssociatedObject 函数中传入的 value 和 policy 这两个值最终是存储在ObjcAssociation 中的。 现在我们已经对 AssociationsManager 、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation 四个对象之间的关系有了简单的认识,那么接下来我们来细读源码,看一下 objc_setAssociatedObject 函数中传入的四个参数分别放在哪个对象中充当什么作用。
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 void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { ObjcAssociation old_association (0 , nil) ; id new_value = value ? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap &associations (manager.associations()) ; disguised_ptr_t disguised_object = DISGUISE(object); if (new_value) { AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.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 { ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); } } else { AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); } } } } if (old_association.hasValue()) ReleaseValue()(old_association); }
细读上述源码我们可以发现,首先根据我们传入的 value 经过 acquireValue 函数处理获取 new_value。acquireValue函数内部其实是通过对策略的判断返回不同的值。
1 2 3 4 5 6 7 8 9 static id acquireValue (id value, uintptr_t policy) { switch (policy & 0xFF ) { case OBJC_ASSOCIATION_SETTER_RETAIN: return objc_retain(value); case OBJC_ASSOCIATION_SETTER_COPY: return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); } return value; }
创建 AssociationsManager manager,以及拿到 manager 内部的 AssociationsHashMap 即 associations。 之后我们看到了我们传入的第一个参数 object,object 经过 DISGUISE 函数被转化为了 disguised_ptr_t 类型的 disguised_object。
1 2 3 typedef uintptr_t disguised_ptr_t ;inline disguised_ptr_t DISGUISE (id value) { return ~uintptr_t (value); }inline id UNDISGUISE (disguised_ptr_t dptr) { return id(~dptr); }
DISGUISE 函数其实仅仅对object做了位运算
之后我们看到被处理成 new_value 的 value,同 policy 被存入了 ObjcAssociation 中。 而 ObjcAssociation 对应我们传入的 key 被存入了 ObjectAssociationMap 中。disguised_object 和 ObjectAssociationMap 则以 key-value 的形式对应存储在 associations 中也就是 AssociationsHashMap 中。
1 2 3 4 5 ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects();
如果我们 value 设置为 nil 的话那么会执行下面的代码
1 2 3 4 5 6 7 8 9 10 AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); } }
从上述代码中可以看出,如果我们设置 value 为 nil 时,就会将关联对象从 ObjectAssociationMap 中移除。
通过上图得出,一个实例对象就对应一个 ObjectAssociationMap,而 ObjectAssociationMap 中存储着多个此实例对象的关联对象的 key 以及 ObjcAssociation,为 ObjcAssociation 中存储着关联对象的 value 和 policy 策略。
由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的 map 用来存放每一个对象及其对应关联属性表格。
objc_getAssociatedObject objc_getAssociatedObject 内部调用的是 _object_get_associative_reference
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 id _object_get_associative_reference(id object, void *key) { id value = nil; uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; { AssociationsManager manager; AssociationsHashMap &associations (manager.associations()) ; disguised_ptr_t disguised_object = DISGUISE(object); AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { ObjcAssociation &entry = j->second; value = entry.value(); policy = entry.policy(); if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) { objc_retain(value); } } } } if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) { objc_autorelease(value); } return value; }
从 _object_get_associative_reference 函数内部可以看出,向 set 方法中那样,反向将 value 一层一层取出最后 return 出去。
objc_removeAssociatedObjects objc_removeAssociatedObjects 用来删除所有的关联对象,objc_removeAssociatedObjects 函数内部调用的是_object_remove_assocations 函数
1 2 3 4 5 6 void objc_removeAssociatedObjects (id object) { if (object && object->hasAssociatedObjects()) { _object_remove_assocations(object); } }
_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()) { ObjectAssociationMap *refs = i->second; for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) { elements.push_back(j->second); } delete refs; associations.erase(i); } } for_each(elements.begin(), elements.end(), ReleaseValue()); }
上述源码可以看出 _object_remove_assocations 函数将 object 对象向对应的所有关联对象全部删除。
总结 关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个 AssociationsManager 中,如果设置关联对象为 nil,就相当于是移除关联对象。此时我们我们在回过头来看 objc_AssociationPolicy 属性的保存策略。
1 2 3 4 5 6 7 typedef OBJC_ENUM (uintptr_t , objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0 , OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1 , OBJC_ASSOCIATION_COPY_NONATOMIC = 3 , OBJC_ASSOCIATION_RETAIN = 01401 , OBJC_ASSOCIATION_COPY = 01403 };
我们会发现其中只有 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 了。
参考链接