当前位置: 首页 > news >正文

【iOS】类扩展与关联对象

目录

前言

类扩展

类扩展的本质

关联对象

设值流程

_object_set_associative_reference 方法

取值流程

objc_getAssociatedObject

关于类扩展和分类


前言

之前已经分析了类和分类的加载,那么这篇文章来讲解一下类扩展和关联对象的底层原理

类扩展

类扩展又称为匿名的分类,可以给当前类添加属性方法

类扩展的本质

写一个类扩展,生成cpp文件,并在文件中查找扩展中添加的属性

可以看到cpp文件中存在带下划线的该成员变量,并且合成了set和get方法,所以说扩展中可以给类添加属性。

再看看扩展中添加的方法:

可以看到扩展中方法已经添加在方法列表中了,也就是说扩展中添加的方法在编译过程中,就已经添加到了methodlist中,作为类的一部分,即编译时期直接添加到本类里面

关联对象

关联对象底层原理的实现,主要就是两个部分:

  • 通过objc_setAssociatedObject设值流程

  • 通过objc_getAssociatedObject取值流程

设值流程

在分类中重写属性cate_name的set、get方法,通过runtime的属性关联方法实现

这里objc_setAssociatedObject方法有四个参数,分别表示:

  • 参数1:要关联的对象,即给谁添加关联属性

  • 参数2:标识符,方便下次查找

  • 参数3:value

  • 参数4:属性的策略,即nonatomic、atomic、assign

这里objc_setAssociatedObject源码实现中调用_object_set_associative_reference 方法。

_object_set_associative_reference 方法

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{// This code used to work when nil was passed for object and key. Some code// probably relies on that to not crash. Check and handle it explicitly.// rdar://problem/44094390if (!object && !value) return;
​if (object->getIsa()->forbidsAssociatedObjects())_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));//object封装成一个数组结构类型,类型为DisguisedPtr//相当于包装了一下 对象object,便于使用DisguisedPtr<objc_object> disguised{(objc_object *)object};ObjcAssociation association{policy, value}; // 包装一下 policy - value
​// retain the new value (if any) outside the lock.association.acquireValue();//根据策略类型进行处理
​bool isFirstAssociation = false;{//初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的AssociationsManager manager;AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一
​AssociationsManager manager2;AssociationsHashMap &associations2(manager2.get());if (value) {auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的结果是一个类对if (refs_result.second) {//判断第二个存不存在,即bool值是否为true/* it's the first association we make */isFirstAssociation = true;//标记true}
​/* establish or replace the association *///得到一个空的桶子,找到引用对象类型,即第一个元素的second值auto &refs = refs_result.first->second;//查找当前的key是否有association关联对象auto result = refs.try_emplace(key, std::move(association));if (!result.second) {//如果结果不存在association.swap(result.first->second);}} else { //如果传的是空值,则移除关联,相当于移除auto refs_it = associations.find(disguised);if (refs_it != associations.end()) {auto &refs = refs_it->second;auto it = refs.find(key);if (it != refs.end()) {association.swap(it->second);refs.erase(it);if (refs.size() == 0) {associations.erase(refs_it);
​}}}}}
​// Call setHasAssociatedObjects outside the lock, since this// will call the object's _noteAssociatedObjects method if it// has one, and this may trigger +initialize which might do// arbitrary stuff, including setting more associated objects.if (isFirstAssociation)object->setHasAssociatedObjects();
​// release the old value (outside of the lock).association.releaseHeldValue();//释放
}

通过_object_set_associative_reference 源码可知方法主要分为以下几步:

  1. 创建一个 AssociationsManager 管理类

  2. 获取唯一的全局静态哈希Map:AssociationsHashMap

  3. 判断 value 是否存在(value为空则走清除逻辑)

  4. 尝试插入对象桶(ObjectAssociationMap):以 disguised(object)(通过类得到的,不同的类名该函数返回值不同) 为 key,尝试插入一个空的 ObjectAssociationMap(如果没有的话)。

  5. 插入桶成功(第二层 map 空)时,插入一个空桶(桶是 key→ObjcAssociation)(key 是用户传进来的 key(void*),value 是封装了 policy + valueObjcAssociation

  6. 第一次插入对象时,通过 setHasAssociatedObjects 设置标记位(如果是这个 object 第一次有关联对象,就设置 isa 标志位的 has_assoc = 1

  7. 用 ObjcAssociation 替换旧值(如果之前已有 key)(如果第二层已经有这个 key,不是第一次插入,就调用 swap

  8. 标记第一次插入 ObjectAssociationMap 的布尔值为 true

这里AssociationsManager类型的变量,会自动调用AssociationsManager的析构函数进行初始化,这个源码中加锁是为了避免多线程重复创建,但并不代表唯一

AssociationsHashMap类型的哈希map,这个是唯一的:

可以看到源码是通过_mapStorage.get()生成哈希map,而_mapStorage是一个静态变量,所以哈希map永远是通过静态变量获取的,所以全场唯一

如果传入的value是空值,那么走else流程,else分支中移除关联

try_emplace方法

try_emplace方法负责完成对全局哈希map中插入新的类以及在类对应的表中插入新的键值对。

这里的桶子负责保存键值之间的映射关系,可以理解为保存值,而这里保存的值的类型取决于方法的调用者:

这里第一次调用try_emplace方法时,是在对全局的哈希表和类操作,可以理解为桶子里存的是类,第二次调用try_emplace方法时,是在对类对应的哈希表和关联对象操作。可以理解为桶子里存的是关联对象。

可以得到大致结构是这样的:

AssociationsManager可以有多个,通过AssociationsManagerLock锁可以得到一个AssociationsHashMap类型的map

map中有很多的关联对象map,类型是ObjectAssociationMap,其中keyDisguisedPtr<objc_object>,例如TCJPerson会对应一个ObjectAssociationMapTCJStudent也会对应一个ObjectAssociationMap.

ObjectAssociationMap哈希表中有很多key-value键值对,其中key的类型为const void *,其实这个key从底层这个方法_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)的参数就可以看出,key是我们关联属性时设置的字符串value的类型为ObjcAssociation。其中ObjcAssociation是用于包装policyvalue的一个类。

以上流程总结如下图:

取值流程

objc_setAssociatedObject类似,关联对象get方法的核心是objc_getAssociatedObject

objc_getAssociatedObject

通过源码可知,主要分为以下几部分

  • 1:创建一个 AssociationsManager 管理类

  • 2:获取唯一的全局静态哈希MapAssociationsHashMap

  • 3:通过find方法根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器

  • 4:如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (policy和value)

  • 5:通过find方法找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value

  • 6:返回 value

关于类扩展和分类

category 类别、分类

  • 专门用来给类添加新的方法

  • 不能给类添加成员属性,添加了成员属性,也无法取到

    • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写settergetter方法

  • 分类中用@property 定义变量,只会生成变量的settergetter方法的声明不能生成方法实现和带下划线的成员变量

extension 类扩展

  • 可以说成是特殊的分类,也可称作匿名分类

  • 可以给类添加成员属性,但是是私有变量

  • 可以给类添加方法,也是私有方法

http://www.xdnf.cn/news/1209295.html

相关文章:

  • 嵌入式学习日志(十一)
  • Kafka——消费者组重平衡全流程解析
  • 数据库-索引
  • 13、select_points_object_model_3d解析
  • 安卓逆向2-安卓刷机和获取root权限和安装LSPosed框架
  • Linux安装ragflow(含一键安装脚本)
  • vue中使用wavesurfer.js绘制波形图和频谱图
  • sqli-labs通关笔记-第25关GET字符注入(过滤or和and 脚本法)
  • buuctf_crypto26-30
  • 基于变频与移相混合控制(PFM+PSM)的全桥LLC谐振变换器仿真模型
  • 车载诊断架构 --- 关于诊断时间参数P4的浅析
  • QML 3D曲面图(Surface3D)技术
  • K-近邻算法(KNN算法)的K值的选取--交叉验证+网格搜索
  • 【C++算法】72.队列+宽搜_二叉树的最大宽度
  • adb reboot 与 adb shell svc power reboot 的区别
  • 【C++】1. C++基础知识
  • 【HTML】浅谈 script 标签的 defer 和 async
  • 企业高性能web服务器
  • EnergyMath芯详科技 EMS4100/MES4000/MES3900
  • 如何保证DoIP的网络安全?
  • 基于 xlsx-js-style 的 Excel 导出工具实现导出excel
  • 40+个常用的Linux指令——下
  • haproxy应用详解
  • 从github同步新项目的两次挫折-2025.7.29
  • 【WRF工具】服务器中安装编译GrADS
  • 信创国产Linux操作系统汇总:从桌面到服务器,百花齐放
  • 【Golang】Go语言Map数据类型
  • 随缘玩 一: 代理模式
  • 计算器4.0:新增页签功能梳理页面,通过IO流实现在用户本地存储数据
  • MySQL数据库 mysql常用命令