iOS 关联对象 objc_setAssociatedObject ,从源码探讨原理,以及释放时机
1.objc_setAssociatedObject
直接上源码,当我们在 OC 层调用set方法的时候,源码直接会调用另个方法
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
assert(object);
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// 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);
}
下面我们来分析下源码
首先
id new_value = value ? acquireValue(value, policy) : nil;
会调用
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;
}
是来判断当前的策略的,就是我们外面会穿进来一个参数,用来标识是引用计数加一还是copy,接着
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
接着拿着 AssociationsManager
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
取出 hashMap,也就是 AssociationsHashMap disguised_ptr_t disguised_object = DISGUISE(object);
拿到当前对象的补码,计算机能够识别的补码,跟着思路继续往下走,看代码
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();
}
这段代码什么意思呢?我们上边拿到了全局的 AssociationsHashMap 这里面是以每个对象的指针按照16位取反得到补码,作为键,然后以ObjectAssociationMap作为value存储的,那么ObjectAssociationMap又是什么呢,
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
这段代码是通过补码找到 ObjectAssociationMap ,然后,
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);
}
从这段代码我们可以猜到,ObjectAssociationMap
中,是以传入的key作为key,然后以ObjcAssociation
作为 value,而 ObjcAssociation
存储的是策略和我们的value,注意我们的value在上面处理过,进行过引用计数加一或者copy操作。源码这个额地方做的就是如果没有就创建一个存进去,如果有就做一下替换。如果看不明白,就从源码的 else 语句,一看便知
else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
如果从我们的 hashmap 中没有找到 ,那么久新创建一个 ObjectAssociationMap
,然后当前全局的 hashMap 的 key为当前对象指针做的补码,你可以理解为以当前对象作为key,然后 以新建的这个 map作为value,然后,这个mao,又以当前的传进来的 key 作为key,以ObjcAssociation作为value,ObjcAssociation 存储着策略和key对应的value,也是传进来的。
不要忽略一个逻辑,就是上面的 如果 new_value 为 nil,
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);
}
}
如果传进来的值为 nil,就会找到对应的 map,然后找到对应的 association 清除,所以说,如果我们给我们的关联对象设置为nil,就就是做了清除操作
我们再来看下 AssociationsManager,
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
AssociationsManager 是全局唯一的,一次只能有一个manager,后面可以知道,里面有一个静态指针 AssociationsHashMap,全局只有一份,程序已启动就会 有一份,无论我们初始化了几个manager,然后我们看他的构造函数和析构函数,是做了一个锁,来保证 manager 全局只有一份,初始化的时候加锁了,然后在想初始化是不行的,只能等析构函数,系统应该是为了保证唯一性,也就是说我们如果正在设置关联对象,这时候又来设置是会被锁拦截的。
1.1总结
1. 首先根据传进来的关联策略,来对传进来的 value 进行处理,策略也是我们穿进去的,是否进行copy
2. 然后初始化全局 `AssociationsManager`,上面分析过我们的manager,如果这时候有另一个对象来初始化做关联对象,那么他是会被阻塞的,阻塞在构造函数,当我们这次初始化完成,调用了析构函数,下一次才会进行。
3. 然后取出manager中的全局唯一的一份 hashMap
4. 然后调用函数,获取当前对象的反码
5. 然后判断根据我们传进来的value和策略生成的新value是否存在
6. 如果存在value,从 hashMap 中,根据反码作为 key ,取出 ObjectAssociationMap
7. 如果找到 map,然后根据我们传入的 key 作为 key、,找到 ObjcAssociation对象
8. 如果 7 找到了 ObjcAssociation,那么就用新值替换旧的值
9. 如果 7 没有找到,就新建一个 ObjcAssociation 对象,以我们的key和value构造,然后以传进来的key,存储到 ObjectAssociationMap 中
10. 如果 6 没有找到,ObjectAssociationMap,就会新建一个 ObjectAssociationMap ,然后根据我们传进来的 key 和 value 生成新的ObjcAssociation对象,然后存储进去
11. 如果5中发现我们传进来的value为nil,就一步步找,如果找到了存储的ObjcAssociation,就将其销毁
2._object_get_associative_reference
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;
}
上面的代码我就不分析了,因为前面已经分析过set,get很简单,只是,需要根据我们设置的策略判断是否需要retail和release
3.释放时机
前面我们已经讲了set 和 get ,那么重头戏就是,关联对象什么时候释放呢?如果你看过我前面的 weak 释放时机 ,你就会明白的,你可以想想一下,我们只有设置关联属性,什么时候去释放过他,那么系统是怎么做的呢?
从我们观看源码可以知道,在我们的对象进行 dealloc 的时候,
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
回去判断我们的 isa 指针是否有弱引用,关联对象等一系列乱七八糟的东西,isa 指针 在我之前的文章也讲过,所以,当我们有关联对象的时候,会执行object_dispose((id)this);
f方法,
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
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;
}
可以看到,有这么一句 if (assoc) _object_remove_assocations(obj);
这个就是自动释放我们的关联对象,
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()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
代码很容易理解,就是找到 map ,然后循环遍历释放我们设置过得 key value,是不是说到这,豁然开朗了。
4.加餐
上面我们说到过,是根据 isa 指针里面的 字段来判断是否设置过关联对象的,那么是咋么设置到 isa 指针里面的呢?
我们在上面有一个流程是这样的,若果没有 map,会创建map
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
赋值完之后,会调用 object->setHasAssociatedObjects();
,
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
设么是 TaggedPointer,我之前文章也讲过,这个地方就是将我们的 isa 指针的 has_assoc 置为1,一直到声明周期结束,我们在dealloc的时候,就是根据这个字段来判断的,是不是又豁然开朗了.