当前位置: 首页 > news >正文

循环引用问题

为什么80%的码农都做不了架构师?>>>   hot3.png

当多个对象相互持有形成一个封闭的环时,循环引用问题随之出现,导致内存泄漏。

解决循环引用问题主要有两个办法,第一个办法是自己明确知道这里会存在循环引用,在合理的位置主动断开环中的一个引用(置为nil),使得对象得以回收。第二个办法就是使用弱引用。

弱引用的实现原理

弱引用的实现原理是这样,系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。 从这个原理中,我们可以看出,弱引用的使用是有额外的开销的。虽然这个开销很小,但是如果一个地方我们肯定它不需要弱引用的特性,就不应该盲目使用弱引用。举个例子,有人喜欢在手写界面的时候,将所有界面元素都设置成 weak 的,这某种程度上与 Xcode 通过 Storyboard 拖拽生成的新变量是一致的。但是我个人认为这样做并不太合适。因为: 我们在创建这个对象时,需要注意临时使用一个强引用持有它,否则因为 weak 变量并不持有对象,就会造成一个对象刚被创建就销毁掉。 大部分 ViewController 的视图对象的生命周期与 ViewController 本身是一致的,没有必要额外做这个事情。 早先苹果这么设计,是有历史原因的。在早年,当时系统收到 Memory Warning 的时候,ViewController 的 View 会被 unLoad 掉。这个时候,使用 weak 的视图变量是有用的,可以保持这些内存被回收。但是这个设计已经被废弃了,替代方案是将相关视图的 CALayer 对应的 CABackingStore 类型的内存区会被标记成 volatile 类型.

1、delegate与环

//ClassA:
@protocol ClssADelegate <NSObject>
- (void)fuck;
@end
@interface ClassA : UIViewController
@property (nonatomic, strong) id <ClssADelegate> delegate;
@end
//ClassB:
@interface ClassB ()<ClassADelegate>
@property (nonatomic, strong) ClassA *classA;
@end
@implementation ClassB
- (void)viewDidLoad {
    [super viewDidLoad]; 
    self.classA = [[ClassA alloc] init];  
    self.classA.delegate = self;
}

如上代码,B强引用A,而A的delegate属性指向B,这里的delegate是用strong修饰的,所以A也会强引用B,这是一个典型的循环引用样例。而解决其的方式大家也都耳熟能详,即将delegate改为弱引用(weak)。

2、block与环

@interface ClassA ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, assign) NSInteger tem;
@end
@implementation ClassA
- (void)viewDidLoad {
    [super viewDidLoad];
    self.block = ^{
        self.tem = 1;
    };  
}

如上代码,self持有block,而堆上的block又会持有self,所以会导致循环引用,这个例子非常好,因为xcode都能检测出来,报出警告:[capturing self strongly in this block is likely to lead to a retain cycle],当然大部分循环引用的情况xcode是不会报警告的。解决这种循环引用的常用方式如下(这种解决方式可以解决大部分block引起的循环引用,但是有一定缺陷,且看下一节):

@interface ClassA ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, assign) NSInteger tem;
@end
@implementation ClassA
- (void)viewDidLoad {
    [super viewDidLoad];
    __weak typeof(self) weakSelf = self
    self.block = ^{
        weakSelf.tem = 1;
    };  
}

3、结论

如上delegate和block引起的循环引用的处理方式,有一个共同的特点,就是使用weak(弱引用)来打破环,使环消失了。所以,可以得出结论,我们可以通过使用将strong(强引用)用weak(弱引用)代替来解决循环引用。

4、解决block循环引用的深入探索

1)weakSelf与其缺陷

//ClassB是一个UIViewController,假设从ClassA pushViewController将ClassB展示出来
@interface ClassB ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, strong) NSString *str;
@end
@implementation ClassB
- (void)dealloc {
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.str = @"111";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakSelf.str);
        });
    };
    self.block();   
}

这里会有两种情况:

若从A push到B,10s之内没有pop回A的话,B中block会执行打印出来111。 若从A push到B,10s之内pop回A的话,B会立即执行dealloc,从而导致B中block打印出(null)。这种情况就是使用weakSelf的缺陷,可能会导致内存提前回收。

2)weakSelf和strongSelf

@interface ClassB ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, strong) NSString *str;
@end
@implementation ClassB
- (void)dealloc {
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.str = @"111";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(self) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongSelf.str);
        });
    };
    self.block();   
}

我们发现这样确实解决了问题,但是可能会有两个不理解的点。

这么做和直接用self有什么区别,为什么不会有循环引用:外部的weakSelf是为了打破环,从而使得没有循环引用,而内部的strongSelf仅仅是个局部变量,存在栈中,会在block执行结束后回收,不会再造成循环引用。 这么做和使用weakSelf有什么区别:唯一的区别就是多了一个strongSelf,而这里的strongSelf会使ClassB的对象引用计数+1,使得ClassB pop到A的时候,并不会执行dealloc,因为引用计数还不为0,strongSelf仍持有ClassB,而在block执行完,局部的strongSelf才会回收,此时ClassB dealloc。 这样做其实已经可以解决所有问题,但是强迫症的我们依然能找到它的缺陷:

block内部必须使用strongSelf,很麻烦,不如直接使用self简便。 很容易在block内部不小心使用了self,这样还是会引起循环引用,这种错误很难发觉。 不要用NSString和NSNumber测试引用计数 最好使用自定义的class.

3)@weakify和@strongify 查看github上开源的libextobjc库,可以发现,里面的EXTScope.h里面有两个关于weak和strong的宏定义。

// 宏定义
#define weakify(...) \
    ext_keywordify \
    metamacro_foreach_cxt(ext_weakify_,, __weak, __VA_ARGS__)
#define strongify(...) \
    ext_keywordify \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wshadow\"") \
    metamacro_foreach(ext_strongify_,, __VA_ARGS__) \
    _Pragma("clang diagnostic pop")

// 用法
@interface ClassB ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, strong) NSString *str;
@end
@implementation ClassB
- (void)dealloc {
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.str = @"111";
    @weakify(self)
    self.block = ^{
        @strongify(self)
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", self.str);
        });
    };
    self.block();   
}

即使写了weak,strong。也得在block里面首先判断strong存在不存在,然后向下进行。 可以看出,这样就完美解决了3中缺陷,我们可以在block中随意使用self。

转载于:https://my.oschina.net/Jacedy/blog/837606

相关文章:

  • ZooKeeper 在硬盘满后,无法再次启动,抛出Last transaction was partial 解决方法
  • 高性能业务架构解决方案(Keepalive+MySQL)
  • 按钮控件数组实现计算器界面
  • runtime
  • dedecms在后台替换文章标题、内容、摘要、关键字
  • 用户
  • 最小联结词组
  • C# 添加、获取及删除PDF附件
  • HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别
  • Flask 5 模板1
  • hibernate主键为字符串的注解
  • spring拦截器
  • C#实现正则表达式
  • mitmproxy
  • tableView选择多项或单选
  • [deviceone开发]-do_Webview的基本示例
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • CSS 专业技巧
  • Java读取Properties文件的六种方法
  • Python爬虫--- 1.3 BS4库的解析器
  • Swoft 源码剖析 - 代码自动更新机制
  • Terraform入门 - 1. 安装Terraform
  • vue--为什么data属性必须是一个函数
  • Zepto.js源码学习之二
  • 初识 webpack
  • 和 || 运算
  • 解决iview多表头动态更改列元素发生的错误
  • 如何选择开源的机器学习框架?
  • 微信开放平台全网发布【失败】的几点排查方法
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • ​第20课 在Android Native开发中加入新的C++类
  • ​香农与信息论三大定律
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • #NOIP 2014#day.2 T1 无限网络发射器选址
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • (3)llvm ir转换过程
  • (C语言)fread与fwrite详解
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (Redis使用系列) SpringBoot中Redis的RedisConfig 二
  • (超简单)构建高可用网络应用:使用Nginx进行负载均衡与健康检查
  • (二)PySpark3:SparkSQL编程
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (二)什么是Vite——Vite 和 Webpack 区别(冷启动)
  • (分布式缓存)Redis持久化
  • (详细版)Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • ... 是什么 ?... 有什么用处?
  • .net2005怎么读string形的xml,不是xml文件。
  • .NET6使用MiniExcel根据数据源横向导出头部标题及数据
  • .net和php怎么连接,php和apache之间如何连接
  • .NET面试题(二)