iOS——分类、扩展和关联对象

前情回顾

回顾一下什么是分类、什么是扩展:
分类(Category)和类扩展(Extension)是两种常见的代码组织方式,用于扩展类的功能。

分类(Category)

分类是一种将类的实现分散到多个源文件的方式。通过使用分类,你可以将一个类的实现分散到多个源文件中。分类可以为现有的类添加新的方法,这使得你可以向现有的类添加自己的方法。
分类不能添加实例变量,只能添加方法。
分类的特性是可以在运行时阶段动态的为已有的类添加新行为。
分类就是对装饰模式的一种具体实现。它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。
分类也可以把framework私有方法公开化

现在我们通过源码看一下分类:

struct _category_t { 
    const char *name;  // 分类的名称
    struct _class_t *cls;  // 分类所属的类
    const struct _method_list_t *instance_methods;  // 分类中定义的实例方法列表
    const struct _method_list_t *class_methods;  // 分类中定义的类方法列表
    const struct _protocol_list_t *protocols;  // 分类实现的协议列表
    const struct _prop_list_t *properties;  // 分类中定义的属性列表
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
  • 从结构体可以知道,分类可以添加属性,但是只会生成这些属性的getter和setter方法的声明,并不会自动实现这些方法。也就是说,如果你在分类中添加了一个属性,你还需要自己去实现这个属性的getter和setter方法。
  • 分类不像类,它没有成员变量列表,所以你不能在分类中添加成员变量。
  • 分类中的方法可以在运行时动态改变,但结构体不能。如果你想要在运行时给原来的结构体添加一个属性,你只能通过关联对象的方式。关联对象的本质是在类的定义之外为类增加额外的存储空间,实现了一种映射关系。

扩展(Extension)

类扩展与分类类似,有时候也被称为匿名分类,但是类扩展可以添加实例变量和属性。与分类不同,类扩展的方法和属性必须在类的主实现文件中实现。扩展中声明的所有方法和属性必须在主类中实现,否则会导致编译错误。此外,类扩展中添加的实例变量默认为@private类型,其使用范围只能在自身类中。
扩展是在编译阶段与该类同时编译的,是类的一部分。扩展中声明的方法只能在该类的@implementation中实现。所以这也就意味着我们无法对系统的类使用扩展。

分类和扩展的区别

  1. 分类原则上只能增加方法,但是也可以通过关联属性增加属性
  2. 拓展可以增加方法和属性,都是私有的。
  3. 扩展只能在自身类中使用,而不是子类或者其他地方。
  4. 类扩展是在编译阶段添加到类中,而分类是在运行时添加到类中

关联对象

在OC中,关联对象是一种动态给对象添加属性的机制。
正如我们知道的,OC的类可以有实例变量和属性,这些都是在编译时期就已经确定的。然而,有时候我们可能希望在运行时给对象动态添加一些属性,就可以用关联对象。
关联对象还可以为分类添加属性:
Category(分类)的底层结构中,没有成员变量(ivar),因此不能给分类添加成员变量;在分类里面声明的属性,只会生成 get/set 方法的声明,没有方法的实现,所以我们不能直接给分类添加成员变量,但是可以间接实现,那就是使用关联对象

关联对象实际上是通过Objective-C的运行时系统实现的。使用以下三个函数来添加、获取或者删除关联对象:

给对象添加关联对象:

void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)

其中objc_AssociationPolicy policy 是指关联策略。
获取对象的关联对象:

id objc_getAssociatedObject(id object, const void * key)

删除对象的所有关联对象:

void objc_removeAssociatedObjects(id object)

使用方法:

  • 创建ZSXPerson类,以及它的分类ZSXPerson+Text
  • ZSXPerson+Text.h中声明想要添加的属性
  • ZSXPerson+Text.m中使用关联对象API实现 get/set 方法

修饰符

在这里插入图片描述

关联对象并不会改变对象的类或类的定义,它们只是在运行时环境中与特定的对象关联在一起。关联对象的生命周期与对象本身的生命周期相同,当对象被销毁时,其关联的对象也会被销毁。

为分类添加关联对象的本质

关联对象的本质

每一个对象的关联对象实际上都存储在AssociationHashMap内。所有对象的关联内容都在同一个全局容器中。
关联对象由AssociationManager管理并在AssociationHashMap存储。
所以说, 假如给分类添加了一个关联对象, 那么该关联内容既不需要分类管理, 也不是由原类管理。

class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsHashMap内部维护了一个ObjectAssociationMap哈希表:

class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

ObjectAssociationMap内部维护了一个ObjcAssociation哈希表:

class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

key的常见用法

使用的get方法的@selecor作为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”);
错误用法举例

static void *MyKey;

  • objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  • objc_getAssociatedObject(obj, MyKey)
    如上写法,没有给*MyKey,他相当于是NULL,这时候,如果再有另一个属性使用Keystatic void *OtherKey;,MyKey和OtherKey都是 NULL,这就相当于把多个属性都与NULLKey关联,明显就出问题了

关联对象的实现原理

学习OC对象本质时,我们知道对象实际被转化成struct ClassName_IMPL结构体,对象的成员变量的值保存在该结构体中。
使用关联对象给分类添加的成员变量,他并不是保存在该结构体下,因为Category的底层结构中并没有ivar
他是存储在全局的统一的一个AssociationsManager中。

核心对象

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

下面来看它们之间的关系:
在这里插入图片描述

AssociationsManager

AssociationsManager 是管理所有关联对象的中心管理器。它负责存储和检索关联对象的整体结构。它是一个单例,确保在整个运行时环境中只有一个实例。

  • 职责:管理和维护所有对象的关联数据。
AssociationsHashMap

AssociationsHashMap 是一个哈希表,用于将对象映射到它们的关联数据。它在内部使用了一个哈希表,这个字段的 key 是被添加关联对象的对象的地址,value 是个ObjectAssociationMap

  • 职责:高效地查找和存储对象与其关联数据之间的映射。
ObjectAssociationMap

ObjectAssociationMap 是一个结构体或类,用于存储单个对象的所有关联数据。key 是我们调用objc_setAssociatedObject时传入的 key,value 是ObjectAssociation

  • 职责:管理一个对象的所有关联键及其对应的关联值。
ObjcAssociation

ObjcAssociation 是具体的关联对象,包含实际的数据以及数据的存储策略,_policy是我们设置的修饰符(比如:OBJC_ASSOCIATION_ASSIGN),_value是我们传入的值value 等。

  • 职责:存储实际的关联数据以及定义数据的内存管理语义。
工作流程
  1. 设置关联对象

    • 调用 objc_setAssociatedObject
    • AssociationsManager 查找或创建与目标对象相关的 ObjectAssociationMap
    • ObjectAssociationMap 中查找或创建对应的 ObjcAssociation
    • 将关联值和存储策略设置到 ObjcAssociation 中。
  2. 获取关联对象

    • 调用 objc_getAssociatedObject
    • AssociationsManager 查找与目标对象相关的 ObjectAssociationMap
    • ObjectAssociationMap 中查找对应的 ObjcAssociation
    • 返回 ObjcAssociation 中存储的关联值。
  3. 移除关联对象

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

在这里插入图片描述

objc_AssociationPolicy的值并没有weak,这点我们在使用的时候需要注意

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/697767.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

svn的使用

【图文详解】入职必备——SVN使用教程-CSDN博客 使用SVNBucket作为服务端,来辅助学习. 什么时候会产生冲突呢? 原本A,B,服务器的版本都一致,都是最新版. A修改文件m,向服务器提交 B修改文件m,向服务器提交,这时候出现了冲突 双击冲突的文件,手动修改

vscode 访问容器的方式

方法一&#xff1a;先连服务器&#xff0c;再转入容器 配置客户机A M1. 客户机A通过 vscode 连接服务器B&#xff0c;再连接容器C 配置vscode的ssh配置文件&#xff1a;~.ssh\config&#xff08;当需要多个不同的连接时&#xff0c;使用 IdentityFile 指定公钥位置&#xff09;…

【解决问题】QApplication: No such file or directory,C++ 使用Qt或项目未正确加载Cmake报错

运行环境&#xff1a; Clion编译&#xff0c;构建C工程项目报错QApplication: No such file or directory 问题描述 QApplication: No such file or directory 引用的#include <QApplication>飘红 解决方案 1、Qt没有安装正确&#xff0c;请使用对应版本的Qt。或编译…

【Java】Java流中的API

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

Vue笔记(二)

Vue&#xff08;一&#xff09;&#xff1a;Vue笔记&#xff08;一&#xff09;-CSDN博客 综合案例&#xff1a;水果购物车 项目功能&#xff1a; 视频链接&#xff1a;034-水果购物车-基本渲染_哔哩哔哩_bilibili 目录结构&#xff1a; index.css .app-container {padding-…

【python】flask 框架

python flask 框架 flask是一个轻量级的python后端框架 (Django, tornado, flask) 官网&#xff1a;欢迎来到 Flask 的世界 — Flask中文文档(3.0.x) 安装&#xff1a;pip install Flask -i https://pypi.douban.com 常识&#xff1a; http,默认端口号为80; https,默认端口号…

【Linux】进程间通信之命名管道

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

linux centos如何安装python3版本但不能影响默认python2版本

在CentOS上安装Python3而不影响系统默认的Python2,具有如何安装呢? 1. 更新系统包 首先,确保系统包是最新的: sudo yum update -y2. 安装EPEL存储库 EPEL(Extra Packages for Enterprise Linux)存储库包含许多额外的软件包,包括Python3: sudo yum install epel-rel…

IDEA:配置Golang的开发环境

1、下载&安装 进入GO的官网下载对应的GO 我们可以下载安装版&#xff0c;不过本人习惯下载解压版&#xff0c;这个因个人而异 2、配置环境变量 GOBIN : %GOROOT%\bin GOPATH : D:\MyGo 工作区间 GOROOT : D:\Program Files\Go GOJDK地址PATH: %GOBIN% ; %GOROOT%\bin ; …

LeetCode 算法:矩阵置零c++

原题链接&#x1f517;&#xff1a;矩阵置零 难度&#xff1a;中等⭐️⭐️ 题目 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1…

stm32MP135裸机编程:修改基于SD卡的FSBL-A用户程序引导程序(boot)

0 参考资料 轻松使用STM32MP13x - 如MCU般在cortex A核上裸跑应用程序.pdf stm32mp135官方开发板原理图&#xff08;mb1635-bdp-v1-0.zip&#xff09; STM32Cube_FW_MP13_V1.0.0 STM32CubeIDE v1.15 1 为什么需要修改FSBL-A用户程序引导程序 FSBL-A用户程序引导程序的作用在《…

PR如何让音频淡入淡出

PR如何让音频淡入淡出 方法一&#xff1a;效果控件关键帧方法二&#xff1a;音频轨道关键帧 以淡入为例&#xff0c;介绍如何设置淡入的两种方法&#xff0c;推荐使用第二种。淡出效果类似。 方法一&#xff1a;效果控件关键帧 选中音频&#xff0c;点击效果控件 在淡入结束的…

Gradio.NET 的简单入门使用

1、最近在网络上由发现了一个好完的东西。 2、Gradio.NET通过简单的C# Web API几行代码就可以实现一网页界面。 3、Python中也有一个Gradio&#xff0c;功能好像都差不多哦&#xff0c;不废话了&#xff0c;我们来开始实操。 4、在Visual Studio 2022 中创建一个 ASP.NET Cro…

Kimichat使用案例012:用Kimichat拆解雷军在小米汽车SU7发布会上的演讲技巧

文章目录 一、介绍二、输入内容三、输出内容四、继续追问五、继续回答六、讲解对比七、对比回答相似之处:不同之处:八、职场人士如何借鉴九、借鉴内容一、介绍 小米SU7发布会可以说是非常成功。雷军的演讲技巧是发布会成功的重要因素之一,很值得借鉴学习。 可以借助Kimichat…

dat.gui图形用户页面

一、导入 1.npm安装 npm install --save dat.gui 引入&#xff1a; // CommonJS: const dat require(dat.gui); // ES6: import * as dat from dat.gui; const gui new dat.GUI(); 二、控制器 <!DOCTYPE html> <html lang"en"> <head><…

振动分析-1-频谱分析的关键步骤及如何看频谱图

振动故障诊断——如何进行振动频谱分析 参考振动频谱分析关键步骤及分析要点 参考怎么看频谱图? 图解滚动轴承故障的频谱波形 1 频谱分析关键步骤 频谱分析准确与否取决于多个方面。 (1)设置仪器频率范围、分辨率等&#xff1b; (2)数据采集位置应根据设备的机械特性、振动传…

Qt——升级系列(Level Five):显示类控件、输入类控件、多元素控件、容器类控件、布局管理器

显示类控件 Label QLabel 可以⽤来显⽰⽂本和图⽚. 核⼼属性如下&#xff1a; 属性 说明 text QLabel 中的⽂本 textFormat ⽂本的格式. • Qt::PlainText 纯⽂本 • Qt::RichText 富⽂本(⽀持 html 标签) • Qt::MarkdownText markdown 格式 • Qt::AutoText 根…

matlab演示银河系转动动画

代码 function GalaxyRotationSimulation()% 参数设置num_stars 1000; % 恒星数量galaxy_radius 1; % 银河系半径rotation_speed 0.05; % 旋转速度% 生成银河系中的恒星分布theta 2 * pi * rand(num_stars, 1); % 角度r galaxy_radius * sqrt(rand(num_stars, 1)); % 半径…

HTML+CSS 交互式开关按钮

效果演示 实现了一个交互式开关按钮的效果,包括一个标签和两个选项(Yes和No),当用户点击其中一个选项时,按钮会发生动画效果,同时选中的选项会被高亮显示。整个按钮的样式采用了渐变背景色、圆角边框、阴影等元素,使得按钮看起来更加美观。 Code HTML <!DOCTYPE ht…

覆盖路径规划经典算法 The Boustrophedon Cellular Decomposition 论文及代码详解

2000年一篇论文 Coverage of Known Spaces: The Boustrophedon Cellular Decomposition 横空出世&#xff0c;解决了很多计算机和机器人领域的覆盖路径问题&#xff0c;今天我来详细解读这个算法。 The Boustrophedon Cellular Decomposition 算法详解 这篇论文标题为"C…