50万奖金+官方证书,深圳国际金融科技大赛正式启动,点击报名 了解详情
写点什么

如何实现 iOS 中的 Associated Object

  • 2019-12-09
  • 本文字数:3737 字

    阅读完需:约 12 分钟

如何实现 iOS 中的 Associated Object

关注仓库,及时获得更新:iOS-Source-Code-Analyze


这一篇文章是对 FBRetainCycleDetector 中实现的关联对象机制的分析;因为追踪的需要, FBRetainCycleDetector 重新实现了关联对象,本文主要就是对其实现关联对象的方法进行分析。


文章中涉及的类主要就是 FBAssociationManager


FBAssociationManager is a tracker of object associations. For given object it can return all objects that are being retained by this object with objc_setAssociatedObject & retain policy.


FBRetainCycleDetector 在对关联对象进行追踪时,修改了底层处理关联对象的两个 C 函数,objc_setAssociatedObjectobjc_removeAssociatedObjects,在这里不会分析它是如何修改底层 C 语言函数实现的,如果想要了解相关的内容,可以阅读下面的文章。


关于如何动态修改 C 语言函数实现可以看动态修改 C 语言函数的实现这篇文章,使用的第三方框架是 fishhook

FBAssociationManager

FBAssociationManager 的类方法 + hook 调用时,fishhook 会修改 objc_setAssociatedObjectobjc_removeAssociatedObjects 方法:


Objective-C


+ (void)hook {#if _INTERNAL_RCD_ENABLED  std::lock_guard<std::mutex> l(*FB::AssociationManager::hookMutex);  rcd_rebind_symbols((struct rcd_rebinding[2]){    {      "objc_setAssociatedObject",      (void *)FB::AssociationManager::fb_objc_setAssociatedObject,      (void **)&FB::AssociationManager::fb_orig_objc_setAssociatedObject    },    {      "objc_removeAssociatedObjects",      (void *)FB::AssociationManager::fb_objc_removeAssociatedObjects,      (void **)&FB::AssociationManager::fb_orig_objc_removeAssociatedObjects    }}, 2);  FB::AssociationManager::hookTaken = true;#endif //_INTERNAL_RCD_ENABLED}
复制代码


将它们的实现替换为 FB::AssociationManager:: fb_objc_setAssociatedObject 以及 FB::AssociationManager::fb_objc_removeAssociatedObjects 这两个 Cpp 静态方法。


上面的两个方法实现都位于 FB::AssociationManager 的命名空间中:


Objective-C


namespace FB { namespace AssociationManager {  using ObjectAssociationSet = std::unordered_set<void *>;  using AssociationMap = std::unordered_map<id, ObjectAssociationSet *>;
static auto _associationMap = new AssociationMap(); static auto _associationMutex = new std::mutex;
static std::mutex *hookMutex(new std::mutex); static bool hookTaken = false;
...}
复制代码


命名空间中有两个用于存储关联对象的数据结构:


  • AssociationMap 用于存储从对象到 ObjectAssociationSet * 指针的映射

  • ObjectAssociationSet 用于存储某对象所有关联对象的集合


其中还有几个比较重要的成员变量:


  • _associationMap 就是 AssociationMap 的实例,是一个用于存储所有关联对象的数据结构

  • _associationMutex 用于在修改关联对象时加锁,防止出现线程竞争等问题,导致不可预知的情况发生

  • hookMutex 以及 hookTaken 都是在类方法 + hook 调用时使用的,用于保证 hook 只会执行一次并保证线程安全


用于追踪关联对象的静态方法 fb_objc_setAssociatedObject 只会追踪强引用:


Objective-C


static void fb_objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) {  {    std::lock_guard<std::mutex> l(*_associationMutex);    if (policy == OBJC_ASSOCIATION_RETAIN ||      policy == OBJC_ASSOCIATION_RETAIN_NONATOMIC) {      _threadUnsafeSetStrongAssociation(object, key, value);    } else {      // We can change the policy, we need to clear out the key      _threadUnsafeResetAssociationAtKey(object, key);    }  }
fb_orig_objc_setAssociatedObject(object, key, value, policy);}
复制代码


std::lock_guard<std::mutex> l(*_associationMutex)fb_objc_setAssociatedObject 过程加锁,防止死锁问题,不过 _associationMutex 会在作用域之外被释放。


通过输入的 policy 我们可以判断哪些是强引用对象,然后调用 _threadUnsafeSetStrongAssociation 追踪它们,如果不是强引用对象,通过 _threadUnsafeResetAssociationAtKeykey 对应的 value 删除,保证追踪的正确性:


Objective-C


void _threadUnsafeSetStrongAssociation(id object, void *key, id value) {  if (value) {    auto i = _associationMap->find(object);    ObjectAssociationSet *refs;    if (i != _associationMap->end()) {      refs = i->second;    } else {      refs = new ObjectAssociationSet;      (*_associationMap)[object] = refs;    }    refs->insert(key);  } else {    _threadUnsafeResetAssociationAtKey(object, key);  }}
复制代码


_threadUnsafeSetStrongAssociation 会以 object 作为键,查找或者创建一个 ObjectAssociationSet * 集合,将新的 key 插入到集合中,当然,如果 value == nil 或者上面 fb_objc_setAssociatedObject 方法中传入的 policy 是非 retain 的就会调用 _threadUnsafeResetAssociationAtKey 重置 ObjectAssociationSet 中的关联对象:


Objective-C


void _threadUnsafeResetAssociationAtKey(id object, void *key) {  auto i = _associationMap->find(object);
if (i == _associationMap->end()) { return; }
auto *refs = i->second; auto j = refs->find(key); if (j != refs->end()) { refs->erase(j); }}
复制代码


同样在查找到对应的 ObjectAssociationSet 之后会擦除 key 对应的值,_threadUnsafeRemoveAssociations 的实现与这个方法也差不多,相较于 reset 方法移除某一个对象的所有关联对象,该方法仅仅移除了某一个 key 对应的值。


Objective-C


void _threadUnsafeRemoveAssociations(id object) {  if (_associationMap->size() == 0 ){    return;  }
auto i = _associationMap->find(object); if (i == _associationMap->end()) { return; }
auto *refs = i->second; delete refs; _associationMap->erase(i);}
复制代码


调用 _threadUnsafeRemoveAssociations 的方法 fb_objc_removeAssociatedObjects 的实现也很简单,利用了上面的方法,并在执行结束后,使用原 obj_removeAssociatedObjects 方法对应的函数指针 fb_orig_objc_removeAssociatedObjects 移除关联对象:


Objective-C


static void fb_objc_removeAssociatedObjects(id object) {  {    std::lock_guard<std::mutex> l(*_associationMutex);    _threadUnsafeRemoveAssociations(object);  }
fb_orig_objc_removeAssociatedObjects(object);}
复制代码

FBObjectiveCGraphElement 获取关联对象

因为在获取某一个对象持有的所有强引用时,不可避免地需要获取其强引用的关联对象;因此我们也就需要使用 FBAssociationManager 提供的 + associationsForObject: 接口获取所有强引用关联对象:


Objective-C


- (NSSet *)allRetainedObjects {  NSArray *retainedObjectsNotWrapped = [FBAssociationManager associationsForObject:_object];  NSMutableSet *retainedObjects = [NSMutableSet new];
for (id obj in retainedObjectsNotWrapped) { FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, obj, _configuration, @[@"__associated_object"]); if (element) { [retainedObjects addObject:element]; } }
return retainedObjects;}
复制代码


这个接口调用我们在上一节中介绍的 _associationMap,最后得到某一个对象的所有关联对象的强引用:


Objective-C


+ (NSArray *)associationsForObject:(id)object {  return FB::AssociationManager::associations(object);}
NSArray *associations(id object) { std::lock_guard<std::mutex> l(*_associationMutex); if (_associationMap->size() == 0 ){ return nil; }
auto i = _associationMap->find(object); if (i == _associationMap->end()) { return nil; }
auto *refs = i->second;
NSMutableArray *array = [NSMutableArray array]; for (auto &key: *refs) { id value = objc_getAssociatedObject(object, key); if (value) { [array addObject:value]; } }
return array;}
复制代码


这部分的代码没什么好解释的,遍历所有的 key,检测是否真的存在关联对象,然后加入可变数组,最后返回。

总结

FBRetainCycleDetector 为了追踪某一 NSObject 对关联对象的引用,重新实现了关联对象模块,不过其实现与 ObjC 运行时中对关联对象的实现其实所差无几,如果对运行时中的关联对象实现原理有兴趣的话,可以看关联对象 AssociatedObject 完全解析这篇文章,它介绍了底层运行时中的关联对象的实现。


这是 FBRetainCycleDetector 系列文章中的第三篇,第四篇也是最后一篇文章会介绍 FBRetainCycleDetector 是如何获取 block 持有的强引用的,这也是我觉得整个框架中实现最精彩的一部分。


关注仓库,及时获得更新:iOS-Source-Code-Analyze


本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/retain-cycle3


2019-12-09 15:53981

评论

发布
暂无评论
发现更多内容

Java程序性能分析:开篇之jps

javalover123

Java 性能优化 性能 后端

98位企业技术高管入学百度AICA 大模型带来AI人才三大能力要求

飞桨PaddlePaddle

人工智能 百度 paddle 百度飞桨

基于eBPF技术的可观测实践探索

谐云

云原生

PoseiSwap 将向 Zepoch 节点持有者发放新一轮空投,生态启动在即

西柚子

豫园股份基于低代码敏捷式开发的实践与落地

明道云

2023-07-02:给定一个1~N的排列,每次将相邻两数相加,可以得到新的序列,长度是N-1 再对新的序列,每次将相邻两数相加,可以得到新的序列,长度是N-2 这样下去可以最终只剩一个数字 比如 :

福大大架构师每日一题

Go 福大大架构师每日一题

华为云下载加速解决方案:让您的下载更快更稳定

YG科技

大厂月入3w+,失业焦虑折磨着我

程序员晚枫

程序员 大厂 焦虑

ChatGLM2-6B环境搭建

IT蜗壳-Tango

在 Go 中如何实现类似 Python 中的 with 上下文管理器

江湖十年

Go 后端

PoseiSwap 将向 Zepoch 节点持有者发放新一轮空投,生态启动在即

股市老人

PoseiSwap 将向 Zepoch 节点持有者发放新一轮空投,生态启动在即

BlockChain先知

PoseiSwap 将向 Zepoch 节点持有者发放新一轮空投,生态启动在即

鳄鱼视界

es笔记四之中文分词插件安装与使用

Hunter熊

中文分词 elasticsearch

边阅读,边成长

少油少糖八分饱

阅读 每天读本书 书评

C++实现对RGB图片进行编码

芯动大师

代码随想录训练营Day03- 链表(上)

jjn0703

AI、机器学习、大模型、生成式AI和安全

啸天

人工智能 机器学习 安全 大模型 ChatGPT

我在AIGC和数字中台方面的架构升级设计

大东(AIP智能体运营专员)

Go 语言 context 都能做什么?

AlwaysBeta

Go Context go面试题

专科学历,有机会转行程序员吗?

程序员晚枫

程序员 面试 转行 学习计划

趣解领域驱动设计-从地心说到日心说

凡语

领域驱动设计

py基础知识点归纳总结

全栈若城

Python 学习 基础

“科创中国”大湾区青年百人会论坛成功举办

飞桨PaddlePaddle

人工智能 百度 paddle 百度飞桨

代码随想录训练营Day04 - 链表(下)

jjn0703

IoTLink版本更新V1.34.0

山东云则信息科技

Java Vue 后端 物联网 前段

如何实现 iOS 中的 Associated Object_语言 & 开发_Draveness_InfoQ精选文章