博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS关联对象技术原理
阅读量:7154 次
发布时间:2019-06-29

本文共 8593 字,大约阅读时间需要 28 分钟。

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 包含的层级关系.

所有的关联属性 和 获取关联属性 移除关联属性都是通过一个 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
{ public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } };复制代码

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

class ObjectAssociationMap : public std::map
{ public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } };复制代码

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
> 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());}复制代码

以上代码看起来并不难 除了一些 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 时候进行加锁保护

好了,我是大兵布莱恩特,欢迎加入博主技术交流群,iOS 开发交流群

转载于:https://juejin.im/post/5b46e5c1f265da0f4559f0f0

你可能感兴趣的文章
OCP知识点讲解 之 LRU链与脏LRU链
查看>>
ASP.NET MVC+Bootstrap个人博客之文章打赏(六)
查看>>
MPLS ××× 理论
查看>>
创建、删除文件夹和文件夹里的文件
查看>>
Powershell 快捷键
查看>>
Exchange 2013之(八)边缘传输服务器高可用
查看>>
HDU1408 盐水的故事
查看>>
Mysql命令行模式如何导出查询结果数据
查看>>
hadoop daemonlog实现
查看>>
iptables实现网络防火墙(一)
查看>>
装了office2007,右键菜单没有新建Word、Excel选项解决方法
查看>>
PDF编辑器技巧之PDF删除其中一页
查看>>
[ext 界面] 初学: 一些项目上的接触
查看>>
linux下jdk开发环境的配置和错误调试
查看>>
zip压缩和unzip解压缩
查看>>
zabbix 监控jvm / resin /tomcat
查看>>
XP高仿win7宽栏风格主题
查看>>
Java源文件命名规范
查看>>
接口封装映射出的一些问题
查看>>
zencart批量表上传后 标题显示为网址 批量修改标题状态 SEO三要素
查看>>