写点什么

如何实现 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:531005

评论

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

云算力挖矿分币系统软件开发内容(案例)

IPFS矿机分币系统开发模板(现成)

鉴释人物丨专访解决方案负责人卜祥敏:直击业务痛点,赋能客户高效业务逻辑

鉴释

解决方案 业务逻辑 静态代码分析

爱奇艺ZoomAI获CCF科学技术奖科技进步杰出奖,技术创新焕新老片,助力经典传承

爱奇艺技术产品团队

Source Map在前端监控中的应用和实践

爱奇艺技术产品团队

大前端

你真的会使用数据库的索引吗?

华为云开发者联盟

索引 查询 聚集索引

云挖矿分币系统软件开发资料(源码)

带你上手全新版本的Webpack 5

华为云开发者联盟

JavaScript json 打包 webpack 模块

Redis大集群扩容性能优化实践

vivo互联网技术

数据库 redis 性能优化 slots

区块链挖矿系统开发公司(现成源码)

现成区块链挖矿系统源码开发

敏捷开发你必须知道的7件事

华为云开发者联盟

敏捷开发 软件开发 团队 Agile PM

Prometheus 查询操作符(一) 二进制操作符

耳东@Erdong

Prometheus 10月月更

搞懂异地多活,看这篇就够了

Kaito

架构 高可用 后端 容灾 异地多活

【LeetCode】 LRU 缓存机制Java题解

Albert

算法 LeetCode 10月月更

Go Switch - Go 中使用 Switch 的六种方式

baiyutang

golang 10月月更

GK架构2 模6作业

Ping

【Flutter 专题】26 易忽略的【小而巧】的技术点汇总 (四)

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 10月月更

网络协议之:加密传输中的NPN和ALPN

程序那些事

网络协议 程序那些事 ALPN NPN

博睿数据智能监测终端全面升级,计算能力强大、用户体验真实性高

博睿数据

在线数字转大写金额工具

入门小站

工具

APISIX 成为 Apache 项目两周年!

API7.ai 技术团队

开源社区 API网关 Apache APISIX

“对比Excel”系列再添新成员,手把手教你用Python实现报表自动化!

博文视点Broadview

云算力挖矿系统开发公司(源码案例)

Android SDK设计规范与心得

轻口味

android sdk 10月月更

【架构实战营】模块九作业

Abner S.

#架构实战营

使用 Apache APISIX 进行集中式身份认证及进阶玩法

API7.ai 技术团队

开源 身份认证 API网关 Apache APISIX

云算力矿机租赁挖矿系统软件开发资料(案例)

你了解微服务的超时传递吗?

万俊峰Kevin

微服务 go-zero 超时 Go 语言 微服务调用链

python 头等对象之一,python 函数那些不一般的用法

梦想橡皮擦

10月月更

2021年9月云主机性能评测报告

博睿数据

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