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

news/2024/7/20 22:52:49 标签: ios, 分类, 关联对象

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

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

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0, //assign
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //strong, nonatomic
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //copy, nonatomic
    OBJC_ASSOCIATION_RETAIN = 01401, //strong, atomic
    OBJC_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_ASSIGN
    objc_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 {
    //获取到warpper
    WeakObjWrapper *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方法SEL
    SEL 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.niftyadmin.cn/n/5368135.html

相关文章

Linux中共享内存(mmap函数的使用)

内存映射的基本使用 内存映射 概念: 使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。 mmap()的优点: 实现了用户空间和内核空间的高效交互方式 优化前:优…

MySQL中的GROUP_CONCAT()函数详解与实战应用

前言 在 MySQL 数据库中,GROUP_CONCAT() 是一个非常实用的聚合函数,主要用于将属于一组的相关行的数据项进行合并并以字符串的形式返回。通过本文,我们将深入理解 GROUP_CONCAT() 函数的用法以及在实际场景中的应用。 1. GROUP_CONCAT()函数…

freertos 源码分析四 任务创建的简单分析

任务创建xTaskCreate 为TCB和TCB栈分配空间, 初始化,加入就绪任务链表 #if ( configSUPPORT_DYNAMIC_ALLOCATION 1 )BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const configSTACK_DEPTH_TYPE usStackDepth,void *…

openkylin(Debian系)安装nginx及安装前需要的准备

前言 现在很多linux系统都可以使用高级包管理工具安装软件了,但是在像是 openkylin这些新系统中,好多软件包虽然有,但是因为其依赖的包还没有做好,所 以安装会提示你一大堆依赖错误。所以还是要自己来编译安装咯。安装前准备&…

Redis(十三)缓存双写一致性策略

文章目录 概述示例 缓存双写一致性缓存按照操作来分,细分2种读写缓存:同步直写策略读写缓存:异步缓写策略双检加锁策略 数据库和缓存一致性更新策略先更新数据库,再更新缓存先更新缓存,再更新数据库先删除缓存&#xf…

java中String类常用API

前言:在学习java的String类的时候,有很多的API需要了解,下面我将举出其中在新手学习时使用频率较大的几个API。 先大体看一下有哪几个:(如图) 目录 1.equals()和 equalsIgnoreCase&…

环境配置:Udacity的Self-Driving项目安装运行

文章目录 前言1.克隆项目2.执行命令3.新建build文件夹4.启动模拟器5.下一步 前言 Udacity的自动驾驶工程师纳米学位项目(Self-Driving Car Engineer Nanodegree Program)是一项面向学习者的前沿技术项目,旨在提供全面的自动驾驶工程师培训。…

Redis——缓存的持久化

1、持久化机制 Redis的所有数据都保存在内存中,如果没有配置持久化功能,Redis重启后数据就会全部丢失,所以需要开启Redis的持久化功能,将数据保存到磁盘上,这样当Redis重启后,可以从磁盘中恢复数据。Redis…