关联对象介绍

关联对象的作用

分类里面,不可以直接为分类添加属性
代理中,不可以直接为代理添加属性

在普通类中,@property (assign, nonatomic) int age;
会做三件事:

  1. 生成age的成员变量
  2. 生成age的get、set方法的声明
  3. 生成age的get、set方法的实现

而在分类中,@property (assign, nonatomic) int age;可以写,但是它的作用只有一个:
生成age的get、set方法的声明

如果想给分类添加属性,则可以使用关联对象
即实现效果:

  1. 手动实现set\get方法
  2. 在set\get方法中,可以存储、取出属性值
    间接实现为分类添加属性的效果

关联对象的使用:

@interface YZPerson : NSObject
@property (assign, nonatomic) int age;
@end@interface YZPerson (Eat)
@property (copy, nonatomic) NSString *name;
@end#import <objc/runtime.h>
@implementation YZPerson (Eat)
- (void)setName:(NSString *)name
{//第一个参数,给谁添加管理对象(self)//第二个参数,是关联对象的key,就是通过key找value//第三个参数,关联的值,很明显是name//第四个参数,关联策略(相当于assign\strong这种作用)objc_setAssociatedObject(self, @selector(setName:), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}- (NSString *)name
{//key需要保持一致return objc_getAssociatedObject(self, @selector(setName:));
}
@endYZPerson *person1 = [[YZPerson alloc] init];
person1.age = 10;
person1.name = @"zhangSan";YZPerson *person2 = [[YZPerson alloc] init];
person2.age = 20;
person2.name = @"liSi";NSLog(@"person1.age = %d, person2.age = %d", person1.age, person2.age);
NSLog(@"person1.name = %@, person2.name = %@", person1.name, person2.name);结果:
2020-02-27 16:26:56.015710+0800 Category[6423:189583] person1.age = 10, person2.age = 20
2020-02-27 16:26:56.015980+0800 Category[6423:189583] person1.name = zhangSan, person2.name = liSi

关联对象值的作用:

关联对象赋值的时候,第二个参数,key的赋值:
第二个参数的值类型:const void * _Nonnull key
在这里插入图片描述

关联对象赋值时,第四个参数objc_AssociationPolicy的取值:
在这里插入图片描述

关联对象的存储:

在这里插入图片描述

  • 关联对象并不是存储在被关联对象本身内存中
  • 关联对象存储在全局的统一的一个AssociationsManager中
  • 设置关联对象为nil,就相当于移除关联对象

关联对象被一个全局的AssociationsManager管理
AssociationsManager里面有一个map
map的key就是关联对象的(第一个参数)对象的内存地址
map的value又是一个map

第二个map里面
key是第二个参数:key
value是 第三个参数和第四个参数

这就可以解读:
每一个分类对象YZPerson+eat,都可以作为map的key
然后在每一个分类里面,又有多个属性name\age,每一个属性,都是一个map的key

移除关联对象

一种是单个移除,只需赋值是传nil即可,也就是第三个参数接收的是nil,然后会进行改属性的擦除操作
objc_setAssociatedObject(self, @selector(setName:), nil, OBJC_ASSOCIATION_COPY_NONATOMIC);

另外一个是移除该对象(YZPerson+eat)中所有的关联对象
void objc_removeAssociatedObjects(id _Nonnull object)


问:关联对象里面怎么没有weak?

本质上是因为关联对象在保存value时,没有将value加入到对象的弱引用表中,所以对象销毁清空弱引用表时,关联对象存的这个指针不在其中,所以不会随着对象销毁而被置为nil。

主要原因在于关联对象的实现机制和weak引用的内存管理策略之间的复杂性。weak属性的引用是自动置为nil的,当所指向的对象被释放时,任何weak引用都会自动清零。这种行为要求运行时维护一个所谓的“弱引用表”来跟踪和更新所有weak引用。如果关联对象直接支持weak关联,那么每次对象释放时,运行时都需要遍历与之相关联的所有对象,更新或清除这些weak关联,这将增加运行时的复杂度和性能负担。

尽管没有直接的weak关联选项,但开发者可以通过一些技巧间接实现类似weak引用的效果。例如,可以使用OBJC_ASSOCIATION_ASSIGN作为关联策略来模拟弱引用,但需要确保在引用的对象被释放时手动清除这种关联,以避免悬挂指针的风险。此外,还可以通过包装一个weak属性的对象作为关联对象,这样就能在对象被释放时自动清零,模拟weak引用的行为。

总之,虽然关联对象没有直接提供weak引用的选项,但这是出于管理复杂性和性能考虑的结果。需要类似weak功能时,可以通过其他方式间接实现。

OC 底层探索 - Association 关联对象评论区


问:如果想实现一个weak属性,怎么做?

在iOS中,由于分类(Category)本身不支持直接添加属性的存储空间,要在分类中添加一个弱引用属性通常需要结合使用关联对象(Associated Object)和一些额外的技巧来实现。下面是一个实现弱引用属性的步骤:

方法一:定义一个中间对象

在这里插入图片描述

  1. 定义一个中间对象

由于objc_setAssociatedObject不直接支持weak关联,你可以通过创建一个中间对象来持有实际的弱引用。这个中间对象将有一个weak属性,用于指向你想要弱引用的对象。

@interface WeakReferenceObject : NSObject
@property (nonatomic, weak) id target;
@end@implementation WeakReferenceObject
@end
  1. 在分类中使用关联对象

在分类中,使用objc_setAssociatedObjectobjc_getAssociatedObject来分别设置和获取这个中间对象,从而间接实现了一个弱引用的属性。

#import <objc/runtime.h>
@interface NSObject (MyCategory)
//弱引用,相当于自己的weakCar
@property (nonatomic, weak) id myWeakProperty;
@end@implementation NSObject (MyCategory)- (void)setMyWeakProperty:(id)myWeakProperty {//获取中间对象WeakReferenceObject *weakReference = [[WeakReferenceObject alloc] init];//中间对象的target,引用weakCar//target为弱引用weakReference.target = myWeakProperty;//OBJC_ASSOCIATION_RETAIN_NONATOMIC强引用objc_setAssociatedObject(self, @selector(myWeakProperty), weakReference, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}- (id)myWeakProperty {//获取中间对象WeakReferenceObject *weakReference = objc_getAssociatedObject(self, @selector(myWeakProperty));//返回的是中间对象的target,即weakCarreturn weakReference.target;
}@end

注意事项:

使用这种方法时,需要注意内存管理和潜在的循环引用问题。由于是通过关联对象机制实现的,还是要确保关联策略正确选择,以避免内存泄露。在这个例子中,我们使用的是OBJC_ASSOCIATION_RETAIN_NONATOMIC,因为我们希望保持对中间WeakReferenceObject对象的强引用,而WeakReferenceObject则对目标对象保持弱引用。

通过这种方式,虽然稍显复杂,但可以在分类中成功添加一个表现为弱引用的属性。

方法二:使用__weak关键字的Block封装

还可以通过一个封装的Block来持有弱引用,这个Block捕获外部变量的弱引用,在需要时返回这个弱引用:

#import <objc/runtime.h>@interface YZPerson (eat)
//弱引用属性
@property (nonatomic, weak) YZCar *weakCar;
//自己定义的block
@property (nonatomic, copy) YZCar *(^myWeakCarBlock)(void);
@end@implementation YZPerson (eat)
- (void)setWeakCar:(YZCar *)weakCar
{__weak YZCar *weakProperty = weakCar;//block赋值,返回的是弱指针指向的weakPropertyYZCar *(^myWeakCarBlock)(void) = ^{ return weakProperty; };//关联对象保存blockobjc_setAssociatedObject(self, @selector(myWeakCarBlock), myWeakCarBlock, OBJC_ASSOCIATION_COPY);
}- (YZCar *)weakCar
{//获取关联对象的blockYZCar *(^myWeakCarBlock)(void) = objc_getAssociatedObject(self, @selector(myWeakCarBlock));//block调用,就是返回值,weakCarreturn myWeakCarBlock ? myWeakCarBlock() : nil;
}
@end

这种方法利用了Block的捕获特性来维护一个弱引

在这里插入图片描述

方法三:使用NSMapTable

这种方法跟关联对象就没啥关系了

NSMapTable是一个灵活的集合类,可以配置键和值的内存管理策略,包括弱引用。可以利用NSMapTable的弱引用特性来实现类似弱引用的属性:

#import <objc/runtime.h>@interface NSObject (MyCategory)
@property (nonatomic, weak) id myWeakProperty;
@end@implementation NSObject (MyCategory)static NSMapTable *weakProperties;+ (void)load {weakProperties = [NSMapTable weakToWeakObjectsMapTable];
}- (void)setMyWeakProperty:(id)myWeakProperty {@synchronized (self) {[weakProperties setObject:myWeakProperty forKey:self];}
}- (id)myWeakProperty {@synchronized (self) {return [weakProperties objectForKey:self];}
}@end

在这个方法中,NSMapTable 用于存储所有对象的弱引用,实现了类似于弱引用属性的效果。

直接使用关联对象的OBJC_ASSOCIATION_ASSIGN修饰可以吗?

不可以
会在代码结束后,再次访问该分类弱引用属性,导致崩溃

- (void)viewDidLoad {[super viewDidLoad];self.p1 = [[YZPerson alloc] init];self.p1.height = 20;YZCar *strongCar = [[YZCar alloc] init];strongCar.speed = 30;strongCar.color = @"white";self.p1.weakCar = strongCar;NSLog(@"1---%f, %@, %f, %@", self.p1.height, self.p1.weakCar, self.p1.weakCar.speed, self.p1.weakCar.color);//1---20.000000, <YZCar: 0x600003a42160>, 30.000000, white
}

然后在其他地方,再次打印:

NSLog(@"2---%f, %@, %f, %@", self.p1.height, self.p1.weakCar, self.p1.weakCar.speed, self.p1.weakCar.color);
直接crash
讨论
有没有可能,在弱引用属性weakCar释放的时候,对weakCar手动进行nil操作?

首先,讨论weakCar什么时候被释放?
一种是person对象销毁,即本来是self.person.weakCar,现在self.person=nil,则weakCar也需要等于nil

一种是person对象不销毁,而weakCar由于没有强制针引用,从而导致weakCar的对象应该被释放

{@autoreleasepool{YZCar *strongCar = [[YZCar alloc] init];self.person.weakCar = strongCar;}NSLog(@"%@", self.person.weakCar);
}

此时,self.person还存在,但是strongCar已经被释放,从而导致weakCar还指向着对应的内存地址,而没有做nil操作

目前想到的办法是:
在Person和Car的dealloc方法里面,做一个通知,通知person+eat方法,进行weakCar释放操作
Person和Car的dealloc方法里面:

- (void)dealloc
{[[NSNotificationCenter defaultCenter] postNotificationName:@"deallocCar" object:nil];NSLog(@"YZPerson-dealloc");
}

YZPerson+eat方法里面,接受通知,执行:

- (void)deallocCar
{if(self.weakCar){objc_setAssociatedObject(self, @selector(setWeakCar:), nil, OBJC_ASSOCIATION_ASSIGN);}
}

但,有问题的是:这个通知,将所有的wearCar都释放了
如果有两个weakCar,一个需要释放,一个不需要释放,会导致两个都被释放
虽然不会崩溃,但是有其他问题,会导致代码不正确,因此,不可以


分类代码:

在分类YZPerson+eat.h文件下:

#import "YZPerson.h"
#import "YZCar.h"@interface YZPerson (eat)
@property (nonatomic, assign) CGFloat height;
@property (nonatomic, weak) YZCar *weakCar;
@end

分类YZPerson+eat.m文件下:

#import "YZPerson+eat.h"
#import "objc/runtime.h"@implementation YZPerson (eat)- (void)setHeight:(CGFloat)height
{objc_setAssociatedObject(self, @selector(setHeight:), @(height), OBJC_ASSOCIATION_ASSIGN);
}- (CGFloat)height
{return [objc_getAssociatedObject(self, @selector(setHeight:)) floatValue];
}- (void)setWeakCar:(YZCar *)weakCar
{objc_setAssociatedObject(self, @selector(setWeakCar:), weakCar, OBJC_ASSOCIATION_ASSIGN);
}- (YZCar *)weakCar
{return objc_getAssociatedObject(self, @selector(setWeakCar:));
}@end

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【论文通读】UFO:A UI-Focused Agent for Windows OS Interaction

UFO&#xff1a;A UI-Focused Agent for Windows OS Interaction 前言AbstractMotivationMethodsExperimentConclusion 前言 Windows客户端第一个JARVIS&#xff0c;利用GPT4 Vision识别截图信息辅助智能体自动化执行操作&#xff0c;作为微软大肆宣传的一篇工作&#xff0c;其…

如何使用 Python 本地客户端操作读写云服务器 Redis 缓存数据库详细教程(更新中)

Redis 基本概述 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的使用 ANSI C 语言编写的、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库&#xff0c;并提供多种语言的 API。它通常被称为数据结构服务器&#xff0c;因为值&#xff08;value…

【OpenCV】 基础入门(一)初识 Mat 类 | 通过 Mat 类显示图像

&#x1f680; 个人简介&#xff1a;CSDN「博客新星」TOP 10 &#xff0c; C/C 领域新星创作者&#x1f49f; 作 者&#xff1a;锡兰_CC ❣️&#x1f4dd; 专 栏&#xff1a;【OpenCV • c】计算机视觉&#x1f308; 若有帮助&#xff0c;还请关注➕点赞➕收藏&#xff…

部署项目遇到的各种问题总结

文章目录 前言一、后端问题 jar包运行出现错误宝塔面板使用jdk17二、数据库问题 版本问题三、前端问题 连不上后端总结 前言 在做完项目之后&#xff0c;为了让别人访问到自己的网站&#xff0c;就需要部署前端后端以及数据库&#xff0c;但是在部署的过程中出现了各种问题和困…

docker 部署 nali 开源 IP 地理信息归属查询软件

前言 早前用到一个小巧开源的 IP 归属地查询软件&#xff0c;官方提供了 Dockerfile&#xff0c;使用了一段时间觉得还不错&#xff0c;非常简单便捷。 部署 docker 启动 由于该项目会在首次启动自动下载 IP 数据库,所以最好通过挂载目录的方式,将数据库目录挂在到本地,避免…

【C语言】2048小游戏【附源码】

欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 一、游戏描述&#xff1a; 2048是一款数字益智类游戏&#xff0c;玩家需要使用键盘控制数字方块的移动&#xff0c;合并相同数字的方块&#xff0c;最终达到数字方块上出现“2048”的目标。 每次移动操作&#xff0c;所…

C++进阶--C++11(1)

C11是C编程语言的一个版本&#xff0c;于2011年发布。C11引入了许多新特性&#xff0c;为C语言提供了更强大和更现代化的编程能力。这篇文章将对C11的一些新增特性进行讲解和实际应用场景。 统一的列表初始化 {}初始化 在C98中&#xff0c;使用{}符号的一般只仅限于对数组和…

基于蚁群算法的三维路径规划(matlab实现)

作品简介 1 理论基础 1.1 三维路径规划问题概述 三维路径规划指在已知三维地图中&#xff0c;规划出一条从出发点到目标点满足某项指标最优&#xff0c;并且避开了所有三维障碍物的三维最优路径。现有的路径规划算法中&#xff0c;大部分算法是在二维规划平面或准二维规划平面…

今日头条signature参数js逆向(爬虫)

今日头条是ajax动态加载 话不多说&#xff0c;直接上代码 windowglobal;window.location{"ancestorOrigins": {},"href": "https://www.toutiao.com/","origin": "https://www.toutiao.com","protocol": "…

连接Redis不支持集群错误,ERR This instance has cluster support disabled,解决方案

1. 问题背景 调整redis的配置后&#xff0c;启动程序时&#xff0c; 会报如下错误&#xff1a; [redis://172.16.0.8xxx]: ERR This instance has cluster support disabledSuppressed: io.lettuce.core.RedisCommandExecutionException: ERR This instance has cluster supp…

【C++】二分查找算法(模板)

重点 只需要记住两点&#xff1a; 1.left right 时&#xff0c;一定就是最终结果&#xff08;包括找不到目标值&#xff09;&#xff0c;无需再次判断&#xff0c;如果判断就会死循环 2.求中点如果是求左端点 mid left (right - left)/2 如果是求右端点 mid left (right -…

MATLAB 自定义均值滤波 (53)

MATLAB 自定义均值滤波 (53) 一、算法介绍二、算法实现1.原理2.代码一、算法介绍 均值滤波,是一种常见的点云平滑算法,改善原始点云的数据质量问题,MATLAB自带的工具似乎不太友好,这里提供自定义实现的点云均值滤波算法,具体效果如下所示: 均值滤波前: 均值滤波后:…

计算机网络 - 基础篇总结

TCP/IP 网络模型有哪几层&#xff1f; 1.应用层 为用户提供应用功能 2.传输层 负责为应用层提供网络支持 使用TCP和UDP 当传输层的数据包大小超过 MSS&#xff08;TCP 最大报文段长度&#xff09; &#xff0c;就要将数据包分块&#xff0c;这样即使中途有一个分块丢失或损坏…

算法学习——LeetCode力扣图论篇2(1020. 飞地的数量、130. 被围绕的区域、827. 最大人工岛)

算法学习——LeetCode力扣图论篇2 1020. 飞地的数量 1020. 飞地的数量 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个大小为 m x n 的二进制矩阵 grid &#xff0c;其中 0 表示一个海洋单元格、1 表示一个陆地单元格。 一次 移动 是指从一个陆地单元格走到另一个相…

【linux】lsof命令使用

1. 功能 lsof list open files, 列出被进程所使用的文件名称。 2. 基础语法 3. 参数含义 参数含义-a过滤出多个选项要同时满足的文件-U仅列出UNIX-like系统的socket文件类型。-u指定用户&#xff0c;比如-u atiaisi&#xff0c;会把用户atiaisi相关的进程使用的文件列出来。…

游戏运营分析:如何在新游戏上线初期实现精细化运营?

一、背景介绍 在当今的手游市场中&#xff0c;每一款新游戏的发布都如同踏上一段充满未知与挑战的探险之旅。游戏刚上线时&#xff0c;运营情况往往如同飘摇的小船&#xff0c;随时可能受到风浪的侵袭。此时&#xff0c;如何准确地找到问题所在&#xff0c;为游戏的健康运营和持…

SAD法(附python实现)和Siamese神经网络计算图像的视差图

1 视差图 视差图&#xff1a;以左视图视差图为例&#xff0c;在像素位置p的视差值等于该像素在右图上的匹配点的列坐标减去其在左图上的列坐标 视差图和深度图&#xff1a; z f b d z \frac{fb}{d} zdfb​ 其中 d d d 是视差&#xff0c; f f f 是焦距&#xff0c; b b…

openGauss学习笔记-254 openGauss性能调优-使用Plan Hint进行调优-子链接块名的hint

文章目录 openGauss学习笔记-254 openGauss性能调优-使用Plan Hint进行调优-子链接块名的hint254.1 功能描述254.2 语法格式254.3 参数说明254.4 示例 openGauss学习笔记-254 openGauss性能调优-使用Plan Hint进行调优-子链接块名的hint 254.1 功能描述 指明子链接块的名称。…

《书生·浦语大模型全链路开源开放体系》学习笔记

书生浦语大模型全链路开源开放体系-学习笔记 大模型成为发展通用人工智能的重要途径专用模型通用大模型 书生大模型开源历程InternLM2回归语言建模的本质主要亮点性能全方位提升强大的内生计算能力 从模型到应用典型流程全链条开源开放体系数据数据集获取预训练微调XTuner 评测…

【Go】四、包名、访问范围控制、标识符、运算符

文章目录 1、_2、包名3、命名大小影响可访问范围4、运算符5、获取终端输入 1、_ 下划线"_"本身在Go中是一个特殊的标识符&#xff0c;称为空标识符用于忽略某个值 1&#xff09;忽略导入的没使用的包 2&#xff09;忽略某个返回值 2、包名 main包是程序的入口包&a…