当前位置: 首页 > 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.dtcms.com/a/303863.html

相关文章:

  • 时序数据库选型指南:为什么IoTDB正在重新定义工业大数据规则?
  • 谷歌采用 Ligero 构建其 ZK 技术栈
  • QML 3D曲面图(Surface3D)技术
  • p5.js 从零开始创建 3D 模型,createModel入门指南
  • Adv. Sci. 前沿:非零高斯曲率3D结构可逆转换!液晶弹性体多级形变新策略
  • VSCode使用Code Runner运行C/C++输出[Done] exited with code=0 in xxx seconds
  • Marin说PCB之POC电路layout设计仿真案例---10
  • 机械学习--线性回归---三个小案例
  • p5.js 矩形rect绘制教程
  • Ubuntu环境下搭建CUDA编程环境
  • Charles中文版使用指南:如何利用抓包工具优化API调试与网络性能
  • Ubuntu20.04安装和配置Samba实现Win11下共享文件夹
  • 「源力觉醒 创作者计划」 百度AI的战略“惊蛰”,一场重塑格局的“破壁行动”
  • 深度学习篇---百度AI Studio模型
  • 2411.按位或最大的最小子数组长度
  • 服务器中涉及节流(Throttle)的硬件组件及其应用注意事项
  • 服务器分布式的作用都有什么?
  • 《Java 程序设计》第 9 章 - 内部类、枚举和注解
  • ClickHouse MergeTree引擎:从核心架构到三级索引实战
  • C++实现黑板模式操作
  • 怎么提升服务器的防攻击能力!
  • 异地协同新玩法!Docker+Neko+cpolar打造云端共享浏览器
  • Sea AI Lab万信逸博士:大模型训练流水线并行四部曲:吞吐、内存、负载均衡与线性扩展
  • 专业Python爬虫实战教程:逆向加密接口与验证码突破完整案例
  • C 语言指针深度解析:从数组指针到指针函数的实战指南
  • 【21】C# 窗体应用WinForm ——图片框PictureBox属性、方法、实例应用
  • 重生之我在暑假学习微服务第四天《Docker-下篇》
  • Intellij Idea--解决Cannot download “https://start.spring.io‘: Connect timedout
  • React面试题目和答案大全
  • 队列算法之【用队列实现栈】