OC属性关联的实现原理
2020-10-27 本文已影响0人
Sweet丶
OC中在分类中添加属性用属性关联技术来实现存取值的:
- (void)setWeight:(int)weight{
objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_ASSIGN);
}
- (int)weight{
return [objc_getAssociatedObject(self, _cmd) intValue];
}
- (void)removeWeight{
objc_removeAssociatedObjects(self);
}
那么系统的属性关联功能是如何实现的呢?
- 属性关联怎么存储对象对应的key和value的?又是怎么取值的?
- 项目中可能会有很多的对象都使用了属性关联,通过什么来管理所有对象的属性关联?
通过objc的相关源码追踪可以查看到:
- 系统通过一个
AssociationsManager
的全局对象来管理所有对象的属性关联,AssociationsManager
内部有一个哈希表AssociationsHashMap
, 以对象disguised_object
为key,value为ObjectAssociationMap
. -
ObjectAssociationMap
哈希表内部存放了该对象所有的关联属性,key为设置属性关联时的key,value为ObjcAssociation
对象。 -
ObjcAssociation
对象中有设置属性关联时的policy
和value。
注意点:
- 如果设置的value为nil,那么会使得
ObjectAssociationMap
哈希表中对应的那个键值对会删除。 - 如果使用
objc_removeAssociatedObjects
会将AssociationsManager
对象的AssociationsHashMap
中该对象的键值对移除。 - 当对象引用计数为0是会进入dealloc,追踪源码可查看到,销毁对象的过程中会销毁对象在
AssociationsHashMap
中键值对. 具体移除看_object_remove_assocations
函数。
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
附:设置属性关联的源码
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
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) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
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 *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
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);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}