48

iOS关联对象技术原理

 5 years ago
source link: http://www.cocoachina.com/ios/20180621/23892.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

iOS 通过 runtime 的 API 可以给分类添加属性,关联属性总共有下边3个 API

///获取某个对象的关联属性
id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}
///给某个对象添加关联属性
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}
///移除对象所有的关联属性
void objc_removeAssociatedObjects(id object)

通过 runtime 的源码可以看出关联属性并没有添加到 category_t(分类)里边,运行时也不会合并到元类对象里边,而是存储在一个全局的AssociationsManager 里边,下边是这个 AssociationsManager 包含的层级关系.

RJfUVvy.png!web

image.png

所有的关联属性 和 获取关联属性 移除关联属性都是通过一个 AssociationsManager来操作,类似于 OC 中 NSFileManager 的角色,通过传递进来的对象作为地址 取出这个对象所对应的关联列表,然后通过key 取出这个关联列表的关联属性 ObjcAssociation, ObjcAssociation 包含了关联策略 和 关联值.

下边我会通过解读源码 来分析AssociationsManager是如何要关联的值和对象建立联系的.

AssociationsManager 是一个 C++的类 用来进行对关联对象的属性添加 和 查找 移除等操作

class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap. 这个_ map 里边存储的有关联列表
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }
    
    AssociationsHashMap &associations() { //可以看成是只初始化一次 类似与单例
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

关联列表是一个 hashMap 类似于 OC 的 NSDictionary ,其中用 disguised_ptr_t 作为 key , ObjectAssociationMap * 作为一个 value

disguised_ptr_t 是 uintptr_t 的类型

intptr_t 和uintptr_t 类型用来存放指针地址。它们提供了一种可移植且安全的方法声明指针,而且和系统中使用的指针长度相同,对于把指针转化成整数形式来说很有用。

可以把disguised_ptr_t理解为一个指针类型的变量

   class AssociationsHashMap : public unordered_map
<disguised_ptr_t nbsp="" objectassociationmap="" disguisedpointerhash="" disguisedpointerequal="" associationshashmapallocator="">
  {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
</disguised_ptr_t>

ObjectAssociationMap 也是一个 HashMap 存放的是 一个 void * key 就是关联属性时传进来的 key , ObjcAssociation 存放的关联属性策略和值的信息

    class ObjectAssociationMap : public std::map
<void nbsp="" objcassociation="" objectpointerless="" objectassociationmapallocator="">
  {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
</void>

ObjcAssociation 关联属性信息类 存放了关联策略 和 传递进来关联的值 id 类型

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; }
    };

下边是对 objc_getAssociatedObject , objc_setAssociatedObject , objc_removeAssociatedObjects 具体实现的分析

objc_setAssociatedObject 添加关联属性的 API

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
   /// 旧的关联对象 因为关联属性时如果传 nil 可能会替换旧的关联属性 ,这就是移除某个关联属性时传 nil 的原因
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
       ///获取关联属性列表 ,取出来的列表是以对象为单位的 ,即某个对象的关联列表 ,这样就可以单独的关联某个对象的关联属性 而不与其他对象隔离开
        AssociationsHashMap &associations(manager.associations()); 
      /// 将要添加关联属性的对象产生一个内存地址 做 key 存储 它的关联属性
        disguised_ptr_t disguised_object = DISGUISE(object);
      /// 如果要关联的值不为空 ,不为空时 就需要判断这个属性和 key 是不是第一天添加 ,即  void *key, id value 都是第一次传递进来 
        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; //取出旧值 后边对这个旧值进行 release 操作
                   ///将新值存放到 key 对应的字典中去 
                    j->second = ObjcAssociation(policy, new_value);
                } else { ///没有旧值直接将新值添加到字典里
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else { 
                  如果 key 对象的字典不存在 就创建一个字典 (hashMap 类似于字典的功能,本文为了方便理解将它称为字典)
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs; 
              ///将要关联属性和策略封装到一个ObjcAssociation类里边 并根据 key 添加到这个字典里
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
          ///如果添加关联的属性为空时 就需要取出之前关联的值 并把它擦除掉 相当于removeObjectForKey 
        ///还是根据对象内存地址找到它的关联属性列表 ,然后通过 key 找到它关联属性的实体(ObjcAssociation这个类) 最后擦除掉 相当于 free 从内存中移除
            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);
}

objc_getAssociatedObject 关联对象取值的操作

id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; 
    {
      ///还是通过 AssociationsManager 找到所有关联对象类别 ,然后通过传入 object 找到某个对象的关联列表 ,然后通过 key 找到这个对象关联属性列表的某个实体(ObjcAssociation) 最后根据关联策略返回这个属性的值 
        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();
                ///取出关联值和策略 发送消息 类似与 [obj retain]
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
            }
        }
    }
   /// 如果这个对象是延时释放的类型 类似与 OC Array String 这些不是 alloc 来的对象 都要执行 [obj autorelease]来释放 
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
    return value;
}

objc_removeAssociatedObjects 移除该对象所有的关联属性列表

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);
       ///如果这个对象有关联的属性列表 那么久便利它关联的属性列表 然后通过便利将这些关联内容 一个个从字典里边擦除  先擦除对象列表关联的属性列表 然后将这个对象关联属性的 hashMap 擦除掉 相当于 [dict removeAllObjects] 然后再从全局 AssociationsManager 移除 这个对象关联的字典 , 又相当于 从一个全局大字典里 把 dict这个对象的小字典 给移除了 
        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());
}
</objcassociation>

以上代码看起来并不难 除了一些 C++ 语法难以理解外 也并不需要完全知道每行代码怎么实现 ,大概思路就是 通过全局大字典 ,找到某个对象相关的小字典 ,然后这个小字典里存放了 一个key 对应一个属性值,最后取出这个管理属性的策略和值

写到最后 AssociationsManager 这个类不是一个单例类

class AssociationsManager {

static spinlock_t _lock;

static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.

public:

AssociationsManager() { _lock.lock(); } //默认无参构造函数 对象创建时自动调用 执行加锁 这样多个线程访问 _map 时不会出现问题

~AssociationsManager() { _lock.unlock(); } //析构函数 对象是否时候进行解锁的操作

///可以看做单例对象 在 AssociationsManager 创建时候 加锁 当 AssociationsManager 释放时候 解锁 ,防止多线程访问时候 对同一个 _map 多次创建 是一种懒汉模式单例

AssociationsHashMap &associations() {

if (_map == NULL)

_map = new AssociationsHashMap();

return *_map;

}

};

它里边有个 spinlock_t锁 对 _map 这个全局唯一的实例 进行加锁和解锁 ,由于懒汉模式的单例 需要在多个线程访问 _map 时候进行加锁保护

作者:大兵布莱恩特

链接:https://www.jianshu.com/p/21555a36a29d


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK