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

【iOS】分类、扩展、关联对象

分类、扩展、关联对象

  • 前言
  • 分类
  • 扩展
  • 扩展和分类的区别
  • 关联对象
    • key的几种用法
    • 流程
  • 总结

前言

最近的学习中笔者发现自己对于分类、扩展相关知识并不是很熟悉,刚好看源码类的加载过程中发现有类扩展与关联对象详解。本篇我们来探索一下这部分相关知识,首先我们要记住扩展是编译时就被添加在类中,而分类是在运行时才被整合到类信息中来的

分类

这里我们先来看看使用Clang编译之后,分类的底层结构struct category_t

在这里插入图片描述

这里我们来看看其中的内容,根据名称我们可以发现其中存储了类指针、实例方法表、类方法表、协议表、属性列表,但是并没有类中有的成员变量表。其实看到这里我们就可以明白,我们不可以在分类中定义成员变量,原因很简单,这里面都没有成员变量表

这里还有一个结论:分类可以声明属性,并可以生成对应的set、get方法,但没有去实现该方法

分类加载流程:

  • 在编译阶段将分类中的方法、属性等编译到一个数据结构category_t
  • 将分类中的方法、属性等合并到一个大数组中去,而后参加编译的分类就会在数组前面
  • 将合并后的分类数据插入到原有数据的前面

故而当分类中的方法与原始类中方法重名的时候,会先去调用分类中实现的方法。

扩展

@interface Person ()@property (nonatomic, assign) NSInteger age;  // 私有属性- (BOOL)validateAge;  // 私有方法声明@end

这里我们将一个扩展直接使用Clang转化位cpp文件,我们可以看到其直接被存储到了成员变量表中,同时方法也直接被添加到了metholist中:

在这里插入图片描述

在这里插入图片描述

故而扩展是在编译阶段与该类同时编译的,是类的一部分。扩展中声明的方法只能在该类的@implementation中实现。所以这也就意味着我们无法对系统的类使用扩展。

扩展和分类的区别

类别、分类

  • 专门用来给类添加新的方法
  • 不能给类添加成员属性,添加了成员属性也无法取到
    • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写settergetter方法
  • 分类中用@property 定义变量,只会生成变量的settergetter方法的声明不能生成方法实现和带下划线的成员变量

扩展

  • 可以说成是特殊的分类,也可称作匿名分类
  • 可以给类添加成员属性,但是是私有变量
  • 可以给类添加方法,也是私有方法

关联对象

这里我们来讲解一下如何通过runtime来给分类添加属性,这里主要分为两部分:

  • 通过objc_setAssociatedObject设值流程
  • 通过objc_getAssociatedObject取值流程

在这里插入图片描述

流程如上所示

我们先来看看取值流程:

objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy)
  • 参数一:要关联的对象,即为谁添加关联属性
  • 参数二:标识符,方便下次查找
  • 参数三:value
  • 参数四:属性的策略,即nonatomic、atomic、assign等,下面展示一下所有关联对象的属性类型:

在这里插入图片描述

下面我们来看看objc_setAssociatedObject的源码实现:

在这里插入图片描述

下面我们进入_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封装成一个数组结构类型,类型为DisguisedPtrDisguisedPtr<objc_object> disguised{(objc_object *)object};//相当于包装了一下 对象object,便于使用// 包装一下 policy - valueObjcAssociation association{policy, value};// retain the new value (if any) outside the lock.association.acquireValue();//根据策略类型进行处理bool isFirstAssociation = false;{//初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化AssociationsManager manager;//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一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;}/* establish or replace the association */auto &refs = refs_result.first->second;//得到一个空的桶子,找到引用对象类型,即第一个元素的second值auto result = refs.try_emplace(key, std::move(association));//查找当前的key是否有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();//释放
}

我们来看看这段源码的实现过程:

  • 首先检查对象所属类是否禁止关联对象(系统类就不可以),若禁止则直接触发崩溃
  • 创建一个全局管理关联对象的AssociationsManager管理类,并获取唯一的全局静态哈希Map:AssociationsHashMap
  • value是否存在:
  • 若存在,通过try_emplace方法,创建一个空的ObjectAssociationMap去取查询键值对
  • 如果发现没有这个 key 就插入一个空的 BucketT进去并返回true
  • 通过setHasAssociatedObjects方法标记对象存在关联对象即置isa指针的has_assoc属性为true
  • 用当前policy 和 value组成了一个ObjcAssociation替换原来BucketT 中的空
  • 标记一下 ObjectAssociationMap 的第一次为 false

AssociationsManager

我们先来看看其源码实现

在这里插入图片描述

这里我们可以看到AssociationsHashMap从静态变量中取出,所以全场唯一

下面我们来看看这AssociationsHashMap以及ObjectAssociationMap的定义

在这里插入图片描述

这里先说一下DenseMap,这个东西时LLVM实现的高性能哈希表,支持快速插入、查找、删除(笔者具体也不会)

  • 先来看看ObjectAssociationMap,他对应的是一个对象的关联属性集合,通过健快速定位到具体的ObjcAssociation

在这里插入图片描述

​ 这里展示一下该结构体内部包含的内容:关联值的引用计数策略与实际值

  • 再来看看AssociationsHashMap,这是一个全局管理所有对象的关联属性的集合,这里键为伪装指针(DisguisedPtr,值为该对象关联属性表ObjectAssociationMap

这里附一张图来讲解这几个表之间的关系

在这里插入图片描述

下面来说一下这几个map之间的联系与不同:

  • AssociationsManager可以有很多个,但是AssociationsHashMap类型的map只能有一个,是通过AssociationsManager来获取的
  • 这个map中有很多个ObjectAssociationMap类型的map,在上文中的讲解中,我们可以明白每个对象都有一个ObjcAssociation,所以每个对象都会有一个自己的ObjectAssociationMap类型的map

key的几种用法

  • 使用的get方法的@selector作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
  • 使用指针的地址作为key
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
  • 使用static作为key
static char MyKey;objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
  • 使用属性名作为key
objc_setAssociatedObject(obj, @“property”, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @“property”);

流程

  • 设置关联对象:

    • 调用objc_setAssociatedObject

    • AssociationsManager查找或创建与目标对象相关的ObjectAssociationMap

    • ObjectAssociationMap中查找或创建对应的 ObjcAssociation

    • 将关联值和存储策略设置到 ObjcAssociation 中。

  • 获取关联对象:

    • 调用objc_setAssociatedObject

    • AssociationsManager查找与目标对象相关的ObjectAssociationMap

    • ObjectAssociationMap中查找对应的 ObjcAssociation

    • 返回ObjcAssociation中存储的关联值

  • 移除关联对象:

    • 调用 objc_removeAssociatedObjectsobjc_setAssociatedObject 设置为 nil。
    • AssociationsManager 查找与目标对象相关的ObjectAssociationMap
    • ObjectAssociationMap 中移除对应的 ObjcAssociation
    • 如果ObjectAssociationMap为空,可能会移除整个映射以释放资源。

这个流程其实也就是上文中_object_set_associative_reference的流程,笔者认为这样理解更好一些,下面再附一张图帮助理解

在这里插入图片描述

总结

关联对象就是一个二层哈希的处理,存取的时候都是两层处理,类似于二维数组:
在这里插入图片描述

相关文章:

【iOS】分类、扩展、关联对象

分类、扩展、关联对象 前言分类扩展扩展和分类的区别关联对象key的几种用法流程 总结 前言 最近的学习中笔者发现自己对于分类、扩展相关知识并不是很熟悉&#xff0c;刚好看源码类的加载过程中发现有类扩展与关联对象详解。本篇我们来探索一下这部分相关知识&#xff0c;首先…...

内蒙古工程系列建设工程技术人才评审条件

关于印发《内蒙古自治区工程系列建设工程专业技术人才职称评审条件》的通知 内蒙古工程系列建设工程技术人才评审条件适用范围 内蒙古工程系列建设工程技术人才评审条件之技术员评审要求 内蒙古工程系列建设工程技术人才评审条件之助理工程师评审要求 内蒙古工程系列建设工程技…...

Elasticsearch超详细安装部署教程(Windows Linux双系统)

文章目录 一、前言二、Windows系统安装部署2.1 环境准备2.2 Elasticsearch安装2.3 安装为Windows服务2.4 Head插件安装2.5 Kibana集成&#xff08;可选&#xff09; 三、Linux系统安装部署3.1 环境准备3.2 Elasticsearch安装3.3 系统优化3.4 启动服务3.5 安全配置&#xff08;可…...

第十六章:数据治理之数据架构:数据模型和数据流转关系

本章我们说一下数据架构&#xff0c;说到数据架构&#xff0c;就很自然的想到企业架构、业务架构、软件架构&#xff0c;因为个人并没有对这些内容进行深入了解&#xff0c;所以这里不做比对是否有相似或者共通的地方&#xff0c;仅仅来说一下我理解的数据架构。 1、什么是架构…...

目标检测DINO-DETR(2023)详细解读

文章目录 对比去噪训练混合查询选择look forward twice 论文全称为&#xff1a;DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection 提出了三个新的方法&#xff1a; 首先&#xff0c;为了改进一对一的匹配效果&#xff0c;提出了一种对比去噪训练方法…...

基于 STM32 的蔬菜智能育苗系统硬件与软件设计

一、系统总体架构 蔬菜智能育苗系统通过单片机实时采集温湿度、光照等环境数据,根据预设阈值自动控制灌溉、补光、通风等设备,实现育苗环境的智能化管理。系统主要包括以下部分: 主控芯片:STM32F103C8T6(32 位 ARM Cortex-M3 单片机,性价比高,适合嵌入式控制)传感器模…...

实现一个带有授权码和使用时间限制的Spring Boot项目

生成和验证授权码记录授权时间和过期时间实现授权逻辑 以下是具体的实现方法&#xff1a; 1. 生成和验证授权码 可以使用加密技术生成和验证授权码。授权码中可以包含有效期等信息&#xff0c;并使用密钥进行签名。 示例代码&#xff1a; java复制代码 import javax.crypt…...

SGlang 推理模型优化(PD架构分离)

一、技术背景 随着大型语言模型&#xff08;LLM&#xff09;广泛应用于搜索、内容生成、AI助手等领域&#xff0c;对模型推理服务的并发能力、响应延迟和资源利用效率提出了前所未有的高要求。与模型训练相比&#xff0c;推理是一个持续进行、资源消耗巨大的任务&#xff0c;尤…...

TuyaOpen横空出世!涂鸦智能如何用开源框架重构AIoT开发范式?

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引子&#xff1a;AIoT开发的“不可能三角”被打破 当AI与物理世界深度融合的浪潮席卷全球&#xff0c;开发者们却始终面临一个“不可能三角”——开发…...

Vue语法【2】

1.插值表达式&#xff1a; 语法规则&#xff1a; {{Vue实例中data的变量名}}使用场景&#xff1a; 插值表达式一般使用在文本内容中&#xff0c;如果是元素的属性内容中则无法使用&#xff1b; 案例&#xff1a; <!DOCTYPE html> <html lang"en"> &l…...

2.2.1 05年T2

引言 本文将从一预习、二自习、三学习、四复习等四个阶段来分析2005年考研英语阅读第二篇文章。为了便于后续阅读&#xff0c;我将第四部分复习放在了首位。 四、复习 方法&#xff1a;错误思路分析总结考点文章梳理 4.1 错题分析 题目&#xff1a;26&#xff08;细节题&…...

每日c/c++题 备战蓝桥杯(修理牛棚 Barn Repair)

修理牛棚 Barn Repair 题解 问题背景与挑战 在一个暴风雨交加的夜晚&#xff0c;Farmer John 的牛棚遭受了严重的破坏。屋顶被掀飞&#xff0c;大门也不翼而飞。幸运的是&#xff0c;许多牛正在度假&#xff0c;牛棚并未住满。然而&#xff0c;为了保护那些还在牛棚里的牛&am…...

6个月Python学习计划 Day 3

&#x1f3af; 今日目标 掌握 while 和 for 循环的使用方式理解 range() 的工作机制实践&#xff1a;打印 1~100、累加、九九乘法表等常见程序逻辑 &#x1f9e0; 学习内容详解 while 循环 i 1 while i < 5:print(f"第 {i} 次循环")i 1&#x1f4cc; 特点&…...

Linux虚拟文件系统(2)

2.3 目录项-dentry 目录项&#xff0c;即 dentry&#xff0c;用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项&#xff0c;就构成了文件系统的目录结构。和上一章中超级块和索引节点不同&#xff0c;目录项并不是实际存在于磁盘上的&#xff0c…...

【数据结构】栈和队列(上)

目录 一、栈&#xff08;先进后出、后进先出的线性表&#xff09; 1、栈的概念及结构 2、栈的底层结构分析 二、代码实现 1、定义一个栈 2、栈的初始化 3、入栈 3、增容 4、出栈 5、取栈顶 6、销毁栈 一、栈&#xff08;先进后出、后进先出的线性表&#xff09; 1、…...

科技赋能·长效治理|无忧树建筑修缮渗漏水长效治理交流会圆满举行!

聚焦行业痛点&#xff0c;共话长效未来&#xff01;5月16日&#xff0c;由无忧树主办的主题为“科技赋能长效治理”的建筑修缮渗漏水长效治理技术交流会在上海圆满举行。来自全国的建筑企业代表、专家学者、技术精英齐聚一堂&#xff0c;共探渗漏治理前沿技术&#xff0c;见证科…...

【闲聊篇】java好丰富!

1、在学习mybatis-plus的文档时&#xff0c;发现引入了solon依赖&#xff0c;才发现这是一个对标spring生态的框架&#xff0c;有意思&#xff01; 还有若依框架&#xff0c;真的好丰富~~~~~~~ 2、今天面试官问我&#xff0c;他说很少遇到用redission做延迟队列的。后面我就反…...

STL中list的模拟

这里写目录标题 list 的节点 —— ListNodelist 的 “导览员” —— ListIteratorlist 的核心 —— list 类构造函数迭代器相关操作容量相关操作 结尾 在 C 的 STL&#xff08;标准模板库&#xff09;中&#xff0c;list 是一个十分重要的容器&#xff0c;它就像一个灵活的弹簧…...

6.3.2图的深度优先遍历

知识总览&#xff1a; 树的先根遍历&#xff1a; 采用递归一直找某个节点的子树直到找不到从上往下找 访问根节点1&#xff0c;1的子树有2、3、4,访问2&#xff0c;2节点子树有5访问5,5没有子树&#xff0c;退回到2,2还有子树6访问6,6没有子树再退回到2,2的子树都被访问了再退…...

畅游Diffusion数字人(30):情绪化数字人视频生成

畅游Diffusion数字人(0):专栏文章导航 前言:仅从音频生成此类运动极具挑战性,因为它在音频和运动之间存在一对多的相关性。运动视频的情绪是多元化的选择,之前的工作很少考虑情绪化的数字人生成。今天解读一个最新的工作FLOAT,可以生成制定情绪化的数字人视频。 目录 贡献…...

UE5 Va Res发送请求、处理请求、json使用

文章目录 介绍发送一个Get请求发送Post请求设置请求头请求体带添json发送请求完整的发送蓝图 处理收到的数据常用的json处理节点 介绍 UE5 自带的Http插件&#xff0c;插件内自带json解析功能 发送一个Get请求 只能写在事件图表里 发送Post请求 只能写在事件图表里 设置…...

关于flutter中Scaffold.of(context).openEndDrawer();不生效问题

原因&#xff1a; 在 Flutter 中&#xff0c;Scaffold.of(context) 会沿着当前的 context 向上查找最近的 Scaffold。如果当前的 widget 树层级中没有合适的 Scaffold&#xff08;比如按钮所在的 context 是在某个子 widget 中&#xff09;&#xff0c;就找不到它。 解决办法…...

【C++】深入理解C++中的函数与运算符重载

文章目录 前言一、什么是重载&#xff1f;1.1 函数重载1.1.1 函数重载的规则1.1.2 示例&#xff1a;函数重载 1.2 运算符重载1.2.1 运算符重载的规则1.2.2 示例&#xff1a;运算符重载 1.2.3 运算符重载的注意事项 二、重载的注意事项2.1 重载的二义性2.2 默认参数和重载2.3 运…...

【读代码】BAGEL:统一多模态理解与生成的模型

一、项目概览 1.1 核心定位 BAGEL是字节跳动推出的开源多模态基础模型,具有70亿激活参数(140亿总参数)。该模型在统一架构下实现了三大核心能力: 多模态理解:在MME、MMBench等9大评测基准中超越Qwen2.5-VL等主流模型文本生成图像:生成质量媲美SD3等专业生成模型智能图像…...

隧道自动化监测解决方案

行业现状 隧道作为一种重要的交通运输通道&#xff0c;不管是缓解交通压力&#xff0c;还是让路网结构更趋于完善&#xff0c;它都有着不可估量的作用。隧道在运营过程中&#xff0c;由于受到材料退化、地震、人为因素等影响会发生隧道主体结构的损坏和劣化。若不及时检修和维护…...

如何通过EventChannel实现Flutter与原生平台的双向通信?

在Flutter开发中,EventChannel是处理单向数据流的核心组件,尤其适用于原生平台(Android/iOS)主动向Flutter端推送实时数据的场景,例如传感器数据、后台任务通知等。虽然EventChannel本身以原生到Flutter的单向通信为主,但结合特定设计模式,仍可实现双向交互。本文将详细…...

游戏引擎学习第307天:排序组可视化

简短谈谈直播编程的一些好处。 上次结束后&#xff0c;很多人都指出代码中存在一个拼写错误&#xff0c;因此这次我们一开始就知道有一个 bug 等待修复&#xff0c;省去了调试寻找错误的时间。 今天的任务就是修复这个已知 bug&#xff0c;然后继续排查其他潜在的问题。如果短…...

java接口自动化初识

简介 了解什么是接口和为什么要做接口测试。并且知道接口自动化测试应该学习哪些技术以及接口自动化测试的落地过程。 一、什么是接口 在这里我举了一个比较生活化的例子&#xff0c;比如我们有一台笔记本&#xff0c;在笔记本的两端有很多插口。例如&#xff1a;USB插口。那…...

工作流引擎-01-Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎,支持现实世界的流程自动化需求

前言 大家好&#xff0c;我是老马。 最近想设计一款审批系统&#xff0c;于是了解一下关于流程引擎的知识。 下面是一些的流程引擎相关资料。 工作流引擎系列 工作流引擎-00-流程引擎概览 工作流引擎-01-Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎&#x…...

时序数据库IoTDB的分片与负载均衡策略深入解析

一、引言 随着数据库服务的业务负载增加&#xff0c;扩展服务资源成为必然需求。扩展方式主要分为纵向扩展和横向扩展。纵向扩展通过增加单台机器的能力&#xff08;如内存、硬盘、处理器&#xff09;来实现&#xff0c;但受限于单台机器的硬件能力。而横向扩展则通过增加更多…...