【iOS分类、关联对象】如何使用关联对象给分类实现一个weak的属性

如何使用关联对象给分类实现一个weak的属性

通过关联对象objc_setAssociatedObject中的策略policy可知,并不支持使用weak修饰对象属性:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {OBJC_ASSOCIATION_ASSIGN = 0, //assignOBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //strong, nonatomicOBJC_ASSOCIATION_COPY_NONATOMIC = 3, //copy, nonatomicOBJC_ASSOCIATION_RETAIN = 01401, //strong, atomicOBJC_ASSOCIATION_COPY = 01403 //copy, atomic
};

思考:能否用assign实现?

weak和assign的区别如下:

  • **assign:**对应的所有权类型为__unsafe_unretained,当修饰对象的时候,修饰的指针指向该对象,不会去持有该对象,也不会使retainCount +1,而在指向的对象被释放时,依然指向原来的对象地址,不会被自动置为nil,所以造成了野指针,是不安全的;
  • **weak:**弱引用,不会影响对象的释放,而当对象被释放时(引用计数为0),所有指向它的弱引用都会自定被置为nil,防止野指针,之后再向该对象发消息也不会崩溃,weak是安全的;

看以下测试代码,使用policy为OBJC_ASSOCIATION_ASSIGN的策略,会发生什么样的情况?

//定义Person类
@interface Person : NSObject
@end
@implementation Person
- (void)dealloc {NSLog(@"Person dealloc");
}
@end@interface Person (Test)
//在分类中声明UIViewController属性,用assign修饰
@property(assign, nonatomic) UIViewController *viewController;
@end@implementation Person (Test)
- (void)setViewController:(UIViewController *)viewController {//利用objc_setAssociatedObject设置值,policy为OBJC_ASSOCIATION_ASSIGNobjc_setAssociatedObject(self, @selector(viewController), viewController, OBJC_ASSOCIATION_ASSIGN);
}
- (UIViewController *)viewController {//取值return objc_getAssociatedObject(self, _cmd);
}
@end

img

使用assign修饰对象,当离开作用域后,产生野指针访问Crash(如图),如何避免这个问题?

1、通过中间对象的方式

1.1、利用OBJC_ASSOCIATION_RETAIN_NONATOMIC + weak来实现;

创建中间类:

@interface WeakObjWrapper : NSObject
@property(weak, nonatomic) id weakObj;
@end
@implementation WeakObjWrapper
- (instancetype)initWithWeakObject:(id)weakObj {if (self = [super init]) {_weakObj = weakObj;}return self;
}
@end

实现属性的setter和getter:

@interface Person (Test)
@property(weak, nonatomic) UIViewController *viewController;
@end
@implementation Person (Test)
- (void)setViewController:(UIViewController *)viewController {WeakObjWrapper *warpper = objc_getAssociatedObject(self, @selector(viewController));//用warpper保存传递进来的值if (!warpper) {//warpper不存在则创建warpper = [[WeakObjWrapper alloc] initWithWeakObject:viewController];}else {//已经存在直接赋值warpper.weakObj = viewController;}//保存的实际上是warpper对象objc_setAssociatedObject(self, @selector(viewController), warpper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIViewController *)viewController {//获取到warpperWeakObjWrapper *warpper = objc_getAssociatedObject(self, _cmd);//取出warpper中的值return warpper.weakObj;
}
@end

objc_setAssociatedObject实际上存储的是WeakObjWrapper对象,对WeakObjWrapper对象产生强引用,WeakObjWrapper对象内部弱持有传递进去的值,保证在对象释放的时候,自动把值设置为nil,避免了崩溃;

1.2、借助OBJC_ASSOCIATION_COPY_NONATOMIC和弱引用block

-(void)setWeakvalue:(NSObject *)weakvalue {__weak typeof(weakvalue) weakObj = weakvalue;typeof(weakvalue) (^block)() = ^(){return weakObj;};objc_setAssociatedObject(self, weakValueKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSObject *)weakvalue {id (^block)() = objc_getAssociatedObject(self, weakValueKey);return block();
}

2、借助runtime

继续使用OBJC_ASSOCIATION_ASSIGN,利用runtime动态的创建传进去值的类的子类,在子类的dealloc中,将objc_setAssociatedObject中的value设置为nil,销毁并移除了关联对象,避免Crash,具体实现如下(具体使用已在注释中说明):

void weak_setAssociatedObject(id _Nonnull object,const void * _Nonnull key,id _Nullable value) {//派生一个子类,类名为WeakObjWrapper+value对应的类名const char *clsName = [[NSString stringWithFormat:@"WeakObjWrapper%@", [value class]] UTF8String];//获取派生的子类Class childCls = objc_getClass(clsName);//如果子类不存在,利用runtime动态的创建一个子类if (!childCls) {childCls = objc_allocateClassPair([value class], clsName, 0);objc_registerClassPair(childCls);}
    //注册dealloc方法SELSEL sel = sel_registerName("dealloc");//获取dealloc对应的类型编码const char *deallocEncoding = method_getTypeEncoding(class_getInstanceMethod([value class], sel));// 注意:内部持有value此处需要弱引用处理一下__weak typeof(value) weakValue = value;// 创建一个指向在调用dealloc方法时调用指定block的函数指针IMP deallocImp = imp_implementationWithBlock(^(id _childCls) {//在子类的dealloc方法中将value设置为nil,避免崩溃objc_setAssociatedObject(object, key, nil, OBJC_ASSOCIATION_ASSIGN);//派生的子类的dealloc方法会被调用,父类的不再被调用,故在此处调用一下父类的((void (*)(id, SEL))(void *)objc_msgSend)(weakValue, sel);});
    //给子类添加dealloc方法class_addMethod(childCls, sel, deallocImp, deallocEncoding);//将value对应的isa指向子类object_setClass(value, childCls);//设置关联对象objc_setAssociatedObject(object, key, value, OBJC_ASSOCIATION_ASSIGN);
}

注意:在派生的子类,添加的实现dealloc的方法中,重新调用一下父类的dealloc保证原有的类的释放关系不被破坏;调用(实现属性的getter和setter):

@interface Person (Test)
@property(assign, nonatomic) UIViewController *viewController;
@end
@implementation Person (Test)
- (void)setViewController:(UIViewController *)viewController {weak_setAssociatedObject(self, @selector(viewController), viewController);
}
- (UIViewController *)viewController {return objc_getAssociatedObject(self, _cmd);
}
@end

总结

关联对象中如何实现weak属性?

  • 关联对象objc_setAssociatedObject中的策略policy可知,并不支持使用weak修饰对象属性;
  • 可以借助中间类(OBJC_ASSOCIATION_RETAIN_NONATOMIC + weak)来实现;
  • 也可以继续使用OBJC_ASSOCIATION_ASSIGN,利用runtime动态的创建传进去值的类的子类,在子类的dealloc中,将objc_setAssociatedObject中的value设置为nil,销毁并移除了关联对象,从而避免了Crash;

参考1:https://www.cnblogs.com/huangzs/p/14479408.html

参考2:https://developer.aliyun.com/article/1321927#:~:text=1%20关联对象objc_setAssociatedObject中的策略policy可知,并不支持使用weak修饰对象属性;%202%20可以借助中间类(OBJC_ASSOCIATION_RETAIN_NONATOMIC,%2B%20weak)来实现;%203%20也可以继续使用OBJC_ASSOCIATION_ASSIGN,利用runtime动态的创建传进去值的类的子类,在子类的dealloc中,将objc_setAssociatedObject中的value设置为nil,销毁并移除了关联对象,从而避免了Crash;

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

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

相关文章

ARM交叉编译搭建SSH

首先搭建好arm-linux交叉编译环境,开发板和主机可以ping通。 一、下载需要的源码 下载zlib: zlib-1.2.3.tar.gz 下载ssl: openssl-0.9.8d.tar.gz 下载ssh: openssh-4.6p1.tar.gz 二、交叉编译 新建目录/home/leo/ssh,并且将三个源码复制到该目录下。…

Android:Volley框架使用

3.15 Volley框架使用 Volley框架主要作为网络请求,图片加载工具。当应用数据量小、网络请求频繁,可以使用Volley框架。 框架Github地址:https://github.com/google/volley Volley框架的简单使用,创建项目Pro_VolleyDemo。将Github上下载Volley框架源代码,volley-master.zi…

Linux中ps/kill/execl的使用

ps命令: ps -aus或者ps -ajx或者 ps -ef可以查看有哪些进程。加上 | grep "xxx" 可以查看名为”xxx"的进程。 ps -aus | grep "xxx" kill命令: kill -9 pid 杀死某个进程 kill -l 查看系统有哪些信号 execl函数&#…

MySQL索引分类

索引可以分为主键索引、唯一索引、常规索引、全文索引 1、主键索引 2、唯一索引 3、常规索引 4、唯一索引 1、主键索引 含义:针对于表中主键创建的索引 特点:默认自动创建,只能有一个 关键词:PRIMARY 2、唯一索引 含义&a…

JavaScript滚动事件

🧑‍🎓 个人主页:《爱蹦跶的大A阿》 🔥当前正在更新专栏:《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 滚动是网页交互不可或缺的一部分。监听页面和元素的滚动事件,可以帮助…

面试复盘——10

前言 又是低级错误挂了的。。自己还是太菜了。 而且这场的录屏没录好,没声音,我靠。 面试 先是自我介绍 先交流了实习项目 腾讯云实习项目:问到了协程当时具体是怎么用的,如何通信、如何控制协程的。 另一个实习项目&#x…

跟着cherno手搓游戏引擎【22】CameraController、Resize

前置: YOTO.h: #pragma once//用于YOTO APP#include "YOTO/Application.h" #include"YOTO/Layer.h" #include "YOTO/Log.h"#include"YOTO/Core/Timestep.h"#include"YOTO/Input.h" #include"YOTO/KeyCod…

力扣刷题之旅:进阶篇(五)—— 动态规划(DP)的妙用

力扣(LeetCode)是一个在线编程平台,主要用于帮助程序员提升算法和数据结构方面的能力。以下是一些力扣上的入门题目,以及它们的解题代码。 --点击进入刷题地址 引言: 在算法的世界中,动态规划&#xff…

ubuntu22.04@laptop OpenCV Get Started: 003_image_resizing

ubuntu22.04laptop OpenCV Get Started: 003_image_resizing 1. 源由2. resize应用Demo3 image_resize3.1 C应用Demo3.2 Python应用Demo3.3 重点过程分析3.3.1 根据宽高调整大小3.3.2 根据比例调整大小3.3.3 根据插值方式调整大小 4. 总结5. 参考资料 1. 源由 在OpenCV中调整图…

[HTTP协议]应用层的HTTP 协议介绍

目录 1.前言 2.使用fiddler抓包来观察HTTP协议格式 3.HTTP协议的基本格式 2.1请求 2,1.1首行 2.1.2请求头 2.1.3空行 2.2响应 2.2.1首行 2.2.2响应头 键值对 ​编辑2.2.3空行 2.2.4载荷(响应正文) 3.认识URL 3.1关于URL encode 1.前言 我们在前面的博客中,简单的…

力扣231. 2 的幂(数学,二分查找,位运算)

Problem: 231. 2 的幂 文章目录 题目描述思路即解法复杂度Code 题目描述 思路即解法 思路1:位运算 1.易验证2的幂为正数; 2.易得2的幂用二进制表示只能有一个位为数字1 3.即将其转换为二进制统计其二进制1的个数 思路2:数学 当给定数n大于1时…

DNS 域名系统——应用层

目录 1 域名系统 DNS 1.1 域名系统 1.2 互联网的域名结构 1.2.1 顶级域名 TLD(Top Level Domain) (1) 国家顶级域名 nTLD (2) 通用顶级域名 gTLD (3) 基础结构域名 (infrastructure domain) 1.3 域名服务器 1.3.1 域名服务器的四种类型 (1…

Springboot拦截器中跨域失效的问题、同一个接口传入参数不同,一个成功,一个有跨域问题、拦截器和@CrossOrigin和@Controller

Springboot拦截器中跨域失效的问题 一、概述 1、具体场景 起因: 同一个接口,传入不同参数进行值的修改时,一个成功,另一个竟然失败,而且是跨域问题拦截器内的request参数调用getHeader方法时,获取不到前端…

JAVA设计模式之代理模式详解

代理模式 1 代理模式介绍 在软件开发中,由于一些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为"代理"的第三者来实现间接访问.该方案对应的设计模式被称为代理模式. 代理模式(Proxy Design Pattern ) 原始定义是:让你能够提供对象的替代…

代码随想录算法训练营29期|day41 任务以及具体任务

第九章 动态规划part03 343. 整数拆分 class Solution {public int integerBreak(int n) {//dp[i] 为正整数 i 拆分后的结果的最大乘积int[] dp new int[n1];dp[2] 1;for(int i 3; i < n; i) {for(int j 1; j < i-j; j) {// 这里的 j 其实最大值为 i-j,再大只不过是重…

单片机学习笔记---DS1302时钟

上一节我们讲了DS1302的工作原理&#xff0c;这一节我们开始代码演示。 新创建一个工程写上框架 我们需要LCD1602进行显示&#xff0c;所以我们要将LCD1602调试工具那一节的LCD1602的模块化代码给添加进来 然后我们开始创建一个DS1302.c和DS1302.h 根据原理图&#xff0c;为了…

Java项目:19 基于SpringBoot的医院管理系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 医院管理系统 分为三个角色 管理员、医生、病人 管理员的主要功能&#xff1a;系统管理、医生管理、患者管理、预约管理、病史管理、住院信息管理、管…

ARM PAC/BTI/MTE三剑客精讲与实战

一、PAC指针认证精讲与实战 思考 1、什么是栈溢出攻击&#xff1f;什么是代码重用攻击&#xff1f;区别与联系&#xff1f; 2、栈溢出攻击的软&硬件缓解技术有哪些&#xff1f;在TF-A&OPTEE上的应用&#xff1f; 3、什么是ROP攻击&#xff1f;对ROP攻击的缓解技术&…

深入浅出TCP/IP协议簇:理论与Python实践

源码分享 https://docs.qq.com/sheet/DUHNQdlRUVUp5Vll2?tabBB08J2 当我们提到网络编程或数据爬取时&#xff0c;了解基础的网络通信协议—TCP/IP协议簇是非常有用的。TCP/IP不是单一的协议&#xff0c;而是一组使互联网工作的协议的集合。在本篇博客中&#xff0c;我们将探讨…

深入解析Linux中HTTP代理的工作原理

亲爱的Linux探险家们&#xff0c;准备好一起探索HTTP代理背后的神秘面纱了吗&#xff1f;在这个数字世界里&#xff0c;HTTP代理就像是一个神秘的中间人&#xff0c;默默地在你和互联网之间穿梭&#xff0c;为你传递信息。那么&#xff0c;这个神秘的中间人到底是如何工作的呢&…