简介

Associated ObjectsObjective-C 2.0Runtime 的特性之一。
<objc/runtime.h> 中定义的三个方法,

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
复制代码

从上面可以看出,
objc_setAssociatedObject 用于给对象添加关联对象,传入 nil 则可以移除已有的关联对象;
objc_getAssociatedObject 用于获取关联对象;
objc_removeAssociatedObjects 用于移除一个对象的所有关联对象。
object:传入关联对象的所属对象,也就是增加成员的实例对象,一般来说传入 self
key:唯一标记,即可以使用 static char 作为 key 值,也可以使用 static void *kAssociatedObjectKey 指针作为 key 值,当然推荐使用 用 selector,使用 getter 方法的名称作为 key 值,可以利用 _cmd 来方便的取出 selector
value:传入关联对象。
policyobjc_AssociationPolicy 是一个 Objective-C 枚举类型,也代表关联策略。

注意:objc_removeAssociatedObjects这个方法会移除一个对象的所有关联对象,一般通过给 objc_setAssociatedObject 函数传入 nil 来移除某个已有的关联对象。

关联策略

OBJC_ASSOCIATION_ASSIGN:弱引用关联对象,一般修饰词为 assignunsafe_unretained
OBJC_ASSOCIATION_RETAIN_NONATOMIC:强引用关联对象,非原子操作,修饰词为 strongnonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC:复制关联对象,非原子操作,修饰词为 copynonatomic
OBJC_ASSOCIATION_RETAIN:强引用关联对象,原子操作,修饰词为 strongatomic
OBJC_ASSOCIATION_COPY:复制关联对象,原子操作,修饰词为 copyatomic

注意:OBJC_ASSOCIATION_ASSIGN 弱引用关联对象,一般修饰词为 assignunsafe_unretainedweak 有区别,当对象销毁时,指针的地址还是存在的,也就是说指针并没有被置为 nil,再次访问会造成野指针。

实现原理

objc_setAssociatedObject

下面我们看下 Runtime 的源码。
以下源码来自于[opensource.apple.com]

void objc_setAssociatedObject(id object, const void *key, id value,
objc_AssociationPolicy policy) {
objc_setAssociatedObject_non_gc(object, key, value, policy);
}
复制代码

通过调用关系,Associated Objects 核心实现在 _object_set_associative_reference 方法里面。

_object_set_associative_reference 函数
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
// 创建一个ObjcAssociation对象
ObjcAssociation old_association(0, nil);
// 通过policy为value创建对应属性,如果policy不存在,则默认为assign
id new_value = value ? acquireValue(value, policy) : nil;
{
// 创建AssociationsManager对象
AssociationsManager manager;
// 在manager取_map成员,其实是一个map类型的映射
AssociationsHashMap &associations(manager.associations());
// 创建指针指向即将拥有成员的Class
// 至此该类已经包含这个关联对象
disguised_ptr_t disguised_object = DISGUISE(object);
// 以下是记录强引用类型成员的过程
if (new_value) {
// break any existing association.
// 在即将拥有成员的Class中查找是否已经存在改关联属性
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
// 当存在时候,访问这个空间的map
ObjectAssociationMap *refs = i->second;
// 遍历其成员对应的key
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 如果存在key,重新更改Key的指向到新关联属性
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
// 否则以新的key创建一个关联
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
// key不存在的时候,直接创建关联
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.
// 这种情况是policy不存在或者为assign的时候
// 在即将拥有的Class中查找是否已经存在Class
// 其实这里的意思就是如果之前有这个关联对象,并且是非assign形的,直接erase
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// 如果有该类型成员检查是否有key
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 如果有key,记录旧对象,释放
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
// 如果存在旧对象,则将其释放
if (old_association.hasValue()) ReleaseValue()(old_association);
}
复制代码

源码中得出结论

  • Associated Objects 是一个 AssociationsManager 的结构体,维护了一个 spinlock_t 锁和一个 _map 的哈希表。
  • _map 哈希表中的键为 disguised_ptr_t
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long uintptr_t;
#endif /* _UINTPTR_T */
复制代码

其实 DISGUISE 函数其实仅仅对 object 做了下位运算,得到一个指向 self 地址的指针,通过这个指针,可以找到对应的 value,即一个 AssociationsHashMap 哈希表。

ObjectAssociationMap
#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
复制代码

AssociationsHashMapkeydisguised_ptr_tValue 则是ObjectAssociationMap
ObjectAssociationMap 中以 keyself 指针,Value 则是 ObjcAssociation

ObjcAssociation
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 函数传入的值。

总结:AssociationsHashMapkey-value 的形式保存从对象的 disguised_ptr_tObjectAssociationMap 的映射,而 ObjectAssociationMap 则保存了从 key 到关联对象 ObjcAssociation 的映射,这个数据结构保存了当前对象对应的所有关联对象,最后的 ObjcAssociation 存储了 policy 以及 value

new_value
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
复制代码

根据 acquireValue 函数,把传入的 value 通过对策略的判断返回新的 new_value

new_value != nil 设置/更新关联对象的值

// break any existing association.
// 在即将拥有成员的Class中查找是否已经存在改关联属性
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
// 当存在时候,访问这个空间的map
ObjectAssociationMap *refs = i->second;
// 遍历其成员对应的key
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 如果存在key,重新更改Key的指向到新关联属性
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
// 否则以新的key创建一个关联
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
// key不存在的时候,直接创建关联
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
复制代码
  • 获取唯一的保存关联对象的哈希表 AssociationsHashMap

  • 使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap

  • 如果没有找到,初始化一个 ObjectAssociationMap,再实例化 ObjcAssociation 对象添加到 Map 中,并调用 setHasAssociatedObjects 方法,表明当前对象已经含有关联对象。

  • 如果找到了对应的 ObjectAssociationMap,遍历其成员对应的 key 是否存在,如果存在 key,重新更改 Key 的指向到新关联属性,否则以新的 key 创建一个关联。

如果 new_value == nil,就要删除对应 key 的关联对象。

// setting the association to nil breaks the association.
// 这种情况是policy不存在或者为assign的时候
// 在即将拥有的Class中查找是否已经存在Class
// 其实这里的意思就是如果之前有这个关联对象,并且是非assign形的,直接erase
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// 如果有该类型成员检查是否有key
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 如果有key,记录旧对象,释放
old_association = j->second;
refs->erase(j);
}
}
复制代码

policy 不存在或者为 assign 的时候,

  • 根据 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap
  • 如果找到了对应的 ObjectAssociationMap,遍历其成员对应的 key 是否存在,如果存在 key,调用 erase 方法,移除关联关系。
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
复制代码

如果存在旧对象,则将其释放。

借助一张图objc_setAssociatedObject 原理

objc_getAssociatedObject

objc_getAssociatedObject 内部调用的是 _object_get_associative_reference

id objc_getAssociatedObject(id object, const void *key) {
return objc_getAssociatedObject_non_gc(object, key);
}
复制代码
_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) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
}
return value;
}
复制代码
  • 获取唯一的保存关联对象的哈希表 AssociationsHashMap

  • 使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap

  • 如果找到了对应的 ObjectAssociationMap,遍历其成员对应的 key 是否存在,如果存在 key,获取 ObjcAssociation

  • 返回关联对象 ObjcAssociation 的值。

objc_removeAssociatedObjects

objc_removeAssociatedObjects 用来删除所有的关联对象,objc_removeAssociatedObjects 函数内部调用的是 _object_remove_assocations 函数。

_object_remove_assocations
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());
}
复制代码
  • 获取唯一的保存关联对象的哈希表 AssociationsHashMap

  • 如果哈希表 AssociationsHashMapsize0,直接 return

  • 使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap

  • 如果找到了对应的 ObjectAssociationMap,遍历其成员对应的 key 是否存在,如果存在 key,然后将所有的关联结构保存到 vector 中。

  • 删除关联关系,释放关联对象。

参考链接

www.jianshu.com/p/79479a09a…
blog.leichunfeng.com/blog/2015/0…

浅谈 Objective-C Associated Objects的更多相关文章

  1. 浅谈Objective—C中的面向对象特性

    Objective-C世界中的面向对象程序设计 面向对象称程序设计可能是现在最常用的程序设计模式.如何开发实际的程序是存在两个派系的-- 面向对象语言--在过去的几十年中,很多的面向对象语言被发明出来 ...

  2. 浅谈objective—C管理内存

    这段时间被导师催着论文,调试各种BUg,也是醉了,发现很大程度上,内存出错,栈溢出,各种悲剧.那么今天就和大家一起对OC的内存管理来个探微吧.Objective-C使用一个保留计数记录了我们所创建的所 ...

  3. 浅谈WebService的版本兼容性设计

    在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...

  4. 浅谈OCR之Onenote 2010

    原文:浅谈OCR之Onenote 2010 上一次我们讨论了Tesseract OCR引擎的用法,作为一款老牌的OCR引擎,目前已经开源,最新版本3.0中更是加入了中文OCR功能,再加上Google的 ...

  5. 【ASP.NET MVC系列】浅谈ASP.NET MVC 路由

    ASP.NET MVC系列文章 [01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操作 ...

  6. 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生

    [转].NET(C#):浅谈程序集清单资源和RESX资源   目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...

  7. 浅谈REST API

    浅谈REST API 说明: 本文部分内容根据其它网络文章编写,如有版权问题请及时通知. 背景 发迹于互联网的REST,在国内国外混得可谓是风生水起,如今又进入电信行业的视野,连TMF都将其作为战略项 ...

  8. c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程

    c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...

  9. 开发技术--浅谈python基础知识

    开发|浅谈python基础知识 最近复习一些基础内容,故将Python的基础进行了总结.注意:这篇文章只列出来我觉得重点,并且需要记忆的知识. 前言 目前所有的文章思想格式都是:知识+情感. 知识:对 ...

  10. 开发工具--浅谈Git

    工具|浅谈Git Git这个工具,是我一直想写文章,终于我实现了我的想法.在我开始写之前,发表一下自己的看法,git只是一个工具,既然已经认定是一个工具,那么一定具备工具这类的共同特征,请用面向对象的 ...

随机推荐

  1. Redis 笔记(二)—— STRING 常用命令

    字符串中不仅仅可以存储字符串,它可以存储以下 3 中类型的值 : 字符串 整数 浮点数 Redis 可以对字符串进行截取等相关操作,对整数.浮点数进行增减操作. 自增自减命令 命令 用例和描述 INC ...

  2. mpvue-新建页面、页面跳转、自适应单位

    1.mpvue怎么新建页面? (1)粘贴复制一个页面文件夹,只需要改文件夹名- 文件名不需要改,main.js里的东西不用动.export default里更改局部顶部栏配置. (2)index.vu ...

  3. 汇编刷题:统计2000H开始的正负数的个数

    DATA SEGMENT ORG 2000H INFO DB 1,2,3,4,5,70H,71H,72H,80H,92H N_NUMS DB 00H P_NUMS DB 00H DATA ENDS C ...

  4. vscode如何安装eslint插件 代码自动修复

    ESlint:是用来统一JavaScript代码风格的工具,不包含css.html等. 方法和步骤: 通常情况下vue项目都会添加eslint组件,我们可以查看webpack的配置文件package. ...

  5. Mac PyCharm之.gitignore 安装设置

    1. 首先安装.ignore 点击 PyCharm >>> Preferences 点击Plugins >>> 在搜索框输入.ignore >>> ...

  6. 360众测考试 Drupal 漏洞 CVE-2018-7600 远程代码执行-复现

    0x00 前言 昨天360众测遇到的一个题 今天自己搭环境复现一下,希望对大家有帮助 0x01 漏洞简介 Drupal是一个开源内容管理系统(CMS),全球超过100万个网站(包括政府,电子零售,企业 ...

  7. Scanner的小细节

    Scanner对象是用来接收键盘输入的数据的,可以接收字符串,数字,浮点数,大数等多种数据. Scanner scanner = new Scanner(System.in); System.out. ...

  8. 复杂Excel转换与导入

    需求 把不同客户提供Excel 直接导入到系统中生成对应的收货单或是出货单.后端创建收货端和出货单的接口已经有现成的webservice或是标准的xml:这类需要做的就是把客户提供不同种类的Excel ...

  9. linux常用命令整理(一)

    1.sort(排序) 典型例题:sort -t: -k3n /etc/passwd 以冒号为分隔符根据第三个域的数字大小进行排序(默认分隔符是空格) 2.uniq(去除文件中的连续重复行) 典型例题: ...

  10. week homework: 大家来找茬

    上周课程主题为用户体验,每位同学也根据自己使用APP的体验,例举出一些手机或电脑客户端软件的bug或用户体验非常不好的地方: Tianfu: GitHub.com:界面不够直观,有许多功能不知道入口在 ...