关联对象介绍

关联对象的作用

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

在普通类中,@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.mzph.cn/news/789289.shtml

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

相关文章

使用 Docker 部署 Puter 云桌面系统

1&#xff09;Puter 介绍 :::info GitHub&#xff1a;https://github.com/HeyPuter/puter ::: Puter 是一个先进的开源桌面环境&#xff0c;运行在浏览器中&#xff0c;旨在具备丰富的功能、异常快速和高度可扩展性。它可以用于构建远程桌面环境&#xff0c;也可以作为云存储服…

Unix运维_如何测试C11和C++11至C17和C++17标准的支持

Unix运维_如何测试C11和C11至C17和C17标准的支持 C语言 标准主要有以下几个版本: K&R C: 这是 C语言 的第一个标准, 由 Dennis Ritchie 和 Brian Kernighan 于 1978 年在《The C Programming Language》一书中定义。K&R C 标准包含了基本的语法, 数据类型, 运算符等,…

C# OpenFileDialog

c#—OpenFileDialog&#xff08;打开文件对话框&#xff09; OpenFileDialog 是 Windows Forms 应用程序中用于打开文件的常用对话框。它提供了一个标准的用户界面&#xff0c;让用户能够浏览文件夹、查看文件列表以及选择一个或多个文件。下面是一个使用 OpenFileDialog 的基本…

codeforces Edu 142 D. Fixed Prefix Permutations 【思维、字典树求LCP】

D. Fixed Prefix Permutations 题意 给定 n n n 个长度为 m m m 的排列 a 1 , a 2 , . . . a n a_1,a_2,...a_n a1​,a2​,...an​ 定义一个排列 p p p 的 价值 为 最大顺序长度 k k k&#xff1a; p 1 1 , p 2 2 , p 3 3 , . . . p k k p_1 1,p_2 2, p_3 3, ...…

在编程中使用中文到底该不该??

看到知乎上有个热门问题&#xff0c;为什么很多人反对中文在编程中的使用&#xff1f; 这个问题有几百万的浏览热度&#xff0c;其中排名第一的回答非常简洁&#xff0c;我深以为然&#xff1a; 在国内做开发&#xff0c;用中文写注释、写文档&#xff0c;是非常好的习惯&…

产品经理功法修炼(2)之专业技能

点击下载《产品经理功法修炼(2)之专业技能》 1. 前言 产品经理的能力修炼并非局限于某一技能的速成,而是需要全面参与到产品的整个生命周期中,通过不断的实践来逐步提升自己的各项能力。尽管在企业的日常运作中,我们不可能身兼数职去扮演每一个角色,但作为产品的核心负…

【适用于福彩3D和体彩排列3】012路直选代码对照表

在我的第6套算法中&#xff0c;我用自己搭建的AI模型&#xff0c;对012路直选进行了预测&#xff0c;但是由于没有对照表&#xff0c;导致很多朋友无法看懂预测结果。众所周知&#xff0c;对于012路直选&#xff0c;共计27种组合方式。我自己进行了组合分类&#xff0c;并赋予相…

建立mysql测试数据

建立一个多数据&#xff0c;多字段的大表&#xff0c;索引有意不全&#xff0c;用于多线程测试 1.建表 /*Navicat Premium Data TransferSource Server : duoSource Server Type : MySQLSource Server Version : 80300 (8.3.0)Source Host : localhost:…

LeetCode 209 长度最小的子数组(滑动窗口,双指针实现)

给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续 子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 示例 1&#xff1a; 输入&…

微知识-git rebase常用的3个场景和2个本质

如何修改历史提交的commit 的msg信息 r &#xff0d; git rebase -i xxxx 其中xxx是需要修改的commit的father , -i 是指交互式 &#xff0d; 将pick 修改为r 表示修改commit msg &#xff0d; 其他的不要动 git rebase的原理是&#xff0c;在交互式界面好比是输入命令&#x…

[C/C++] -- 二叉树

1.简介 二叉树是一种每个节点最多有两个子节点的树结构&#xff0c;通常包括&#xff1a;根节点、左子树、右子树。 满二叉树&#xff1a; 如果一棵二叉树只有度为0的结点和度为2的结点&#xff0c;并且度为0的结点在同一层上&#xff0c;则这棵二叉树为满二叉树。深度为k&a…

创研杯赛事:激发英语热情,助力人才培养

2024 中国翻译协会年会期间&#xff0c;知名赛事活动平台赛氪承办的“AI 科技时代竞赛与就业”分论坛&#xff0c;于 3 月 30 日下午在长沙圆满落幕。其中值得瞩目的是&#xff0c;第三届”创研杯“大赛也在本次会议上进行了启动。 创研杯赛事由空中英语教室杂志社发起主办&am…

【解决问题】排查linux文件手动删除文件,但是文件标记为deleted,资源未释放

背景&#xff1a; 生产环境我们把程序生成的数据文件手动删除后&#xff0c;但是空间并没有释放&#xff0c;导致硬盘被占用&#xff0c;不够用 问题排查&#xff1a; 1.查看占用文件状态 使用命令&#xff1a; lsof | grep deleted 查看 文件已经删除了&#xff0c;但是都是…

Oracle学习之路:从小白到专家

一、引言 A. 博客文章的目的和背景 本博客的目的是为那些想要学习Oracle数据库的人提供一个初步的指南&#xff0c;帮助他们从小白到专家的过程。Oracle是一种广泛使用的关系型数据库管理系统&#xff0c;具有广泛的应用场景&#xff0c;因此深入学习Oracle是一个有意义的投资…

C语言 | Leetcode C语言题解之第5题最长回文子串

题目&#xff1a; 题解&#xff1a; char* longestPalindrome(char* s) {int lenstrlen(s),max0;int p0;for(int i0;i<len;i)//这种是判断奇数回文{int lefti-1,righti1;//left左边&#xff0c;right右边while(left>0&&right<len&&s[left]s[right]){/…

自定义多阶段倒计时实现分段倒计时

直接贴代码好了 情况是这么个情况 老板想要一个倒计时完毕后再接下一个倒计时总共四五个算一轮业务结束的这个样子 然后循环执行这个业务,这些循环执行我就用了xxl-job ,整体业务就用信号量执行了,总的来说是返回给前端的时间是零误差的, 业务处理的都用异步去执行保证时间总的…

【JavaScript】使用 NVM 管理 Node.js 版本

风决定要走 云怎么挽留 曾经抵死纠缠放空的手 情缘似流水覆水总难收 我还站在你离开 离开的路口 你既然无心 我也该放手 何必痴痴傻傻纠缠不休 是情深缘浅 留一生遗憾 还是情浅缘深 一辈子怨偶 &#x1f3b5; 庄心妍《以后的以后》 Node.js 是一个广泛使用…

10.图像高斯滤波的原理与FPGA实现思路

1.概念 高斯分布 图像滤波之高斯滤波介绍 图像处理算法|高斯滤波   高斯滤波(Gaussian filter)包含很多种&#xff0c;包括低通、高通、带通等&#xff0c;在图像上说的高斯滤波通常是指的高斯模糊(Gaussian Blur)&#xff0c;是一种高斯低通滤波。通常这个算法也可以用来模…

FME学习之旅---day19

我们付出一些成本&#xff0c;时间的或者其他&#xff0c;最终总能收获一些什么。 如何使用 Esri 模板地理数据库 在学习之初&#xff0c;首先了解什么是Esri模板、如何使用Esri模板以及如何创建Esri模板 有两种类型的 Esri 模板&#xff1a;文件地理数据库 &#xff08;.gd…

环信IM集成教程——Web端UIKit快速集成与消息发送

写在前面&#xff1a; 千呼万唤始出来&#xff0c;环信Web端终于出UIKit了&#xff01;&#x1f389;&#x1f389;&#x1f389; 文档地址&#xff1a;https://doc.easemob.com/uikit/chatuikit/web/chatuikit_overview.html 环信单群聊 UIKit 是基于环信即时通讯云 IM SDK 开…