Block循环引用详解
前言
在项目中经常用到block,使用不当就很容易因为循环引用而造成内存泄漏。本文分析了block循环引用形成原因以及处理办法,如果有什么不对或者疑问请留言。
什么情况下block会造成循环引用
block为了保证代码块内部对象不被提前释放,会对block中的对象进行强引用,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,就会造成循环引用。
常见误区
1.所有block都会造成循环引用
在block中,并不是所有的block都会循造成环引用,比如UIView动画block、Masonry添加约束block、AFN网络请求回调block等。UIView动画block不会造成循环引用是因为这是类方法,不可能强引用一个类,所以不会造成循环引用。Masonry约束block不会造成循环引用是因为Masonry中设置布局的方法中的block对象并没有被外部View所引用,超出当前作用域后,block会被释放,所以我们使用Masonry的时候不需要担心循环引用。
- Masonry内部代码
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
//这里并不是self.block
block(constraintMaker);
return [constraintMaker install];
}
AFN请求回调block不会造成循环引用是因为在内部做了处理。block先是被AFURLSessionManagerTaskDelegate对象持有。而AFURLSessionManagerTaskDelegate对象被mutableTaskDelegatesKeyedByTaskIdentifier字典持有,在block执行完成后,mutableTaskDelegatesKeyedByTaskIdentifier字典会移除AFURLSessionManagerTaskDelegate对象,这样对象就被释放了,所以不会造成循环引用。
- AFN内部代码
#pragma mark - 添加代理
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
//block被代理引用
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
//设置代理
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
#pragma mark - 设置代理
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
//代理被mutableTaskDelegatesKeyedByTaskIdentifier字典引用
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
#pragma mark - 任务完成
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
if (delegate) {
//任务完成,移除
[self removeDelegateForTask:dataTask];
[self setDelegate:delegate forTask:downloadTask];
}
if (self.dataTaskDidBecomeDownloadTask) {
self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
}
}
#pragma mark - 移除任务代理
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
[self.lock lock];
[delegate cleanUpProgressForTask:task];
[self removeNotificationObserverForTask:task];
//移除
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
[self.lock unlock];
}
2.block中只有self会造成循环引用
在block中并不只是self会造成循环引用,用下划线调用属性(如_name)也会出现循环引用,效果和使用self是一样的。
//会造成循环引用
_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"张三";
_person1.block = ^{
NSLog(@"%@",_person2.name)
};
3.weakSelf可以解决所有block造成的循环引用
大部分情况下,这样使用是可以解决block循环引用,但是有些情况下这样使用会造成一些问题,比如在block中延迟执行一些代码,在还没有执行的时候,控制器被销毁了,这样控制器中的对象也会被释放,__weak对象就会变成null。所以会输出null。
//在延迟执行期间,控制器被释放了,打印出来的会是**(null)**
_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"张三";
__weak __typeof(self) weakSelf = self;
_person1.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.person2.name);
});
};
4.用self调用带有block的方法会引起循环引用
并不是所有通过self调用带有block的方法会引起循环引用,需要看方法内部有没有持有self。
//不会引起循环引用
[self dismissViewControllerAnimated:YES completion:^{
NSLog(@"%@",self.string);
}];
5.类方法不会造成循环引用
这是一种错误的说法,类方法我们需要知道其内部是否持有对象,如果内部持有对象,并且在block执行后未释放持有的对象,那么也会造成循环引用。
如何避免循环引用
1.weakSelf、strongSelf结合使用
使用weakSelf结合strongSelf的情况下,能够避免循环引用,也不会造成提前释放导致block内部代码无效。
_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"张三";
__weak __typeof(self) weakSelf = self;
_person1.block = ^{
__typeof(&*weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.person2.name);
});
};
2.block的外部对象使用week
外部对象通过week修饰,使用全局弱指针指向一个局部强引用对象,这样局部变量在超出其作用域后也不会被销毁,因为是弱指针,所以不会造成循环引用。
@interface CLViewController ()
//弱引用指针
@property (nonatomic,weak) Person *person1;
@property (nonatomic,strong) Person *person2;
@end
@implementation CLViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
//局部强引用对象
Person *person1 = [[Person alloc] init];
_person1 = person1;
_person2 = [[Person alloc] init];
_person2.name = @"张三";
_person1.block = ^{
NSLog(@"%@",self.person2.name);
};
}
3.将对象置为nil
使用完对象之后就没有必要再保留该对象了,将对象置为nil。在ARC中,被置为nil的对象会被销毁。虽然这样也可以达到破除循环引用的效果,但是这样使用起来很不方便,如果一个对象有多个block,在前面将对象置空,后面的block就不会执行,所以使用这种方法来防止循环引用,需要注意在合适的位置将对象置空。
_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"张三";
_person1.block = ^{
NSLog(@"%@",self.person2.name);
//置空,避免循环引用
_person1 = nil;
};
//由于上面已经将对象置空,所以这里block里边的代码不会执行
_person1.block = ^{
NSLog(@"%@",self.person2.name);
};
虽然这种方式使用起来不是很友好,但是在封装block的时候,可以考虑使用完马上置空当前使用的block,这样使用的时候就不需要考虑循环引用的问题。
- (void)back
{
if (self.BackBlock)
{
self.BackBlock(button);
}
//使用完,马上置空当前block
self.BackBlock = nil;
}
总结
循环引用的本质是当前对象对这个block进行了强持有,并且block内部对这个对象进行了强持有。使用block的时候,我们首先需要做的就是判断当前block是否会引起循环引用,如果会引起循环引用,再考虑采取哪种方式来避免循环引用。
Block循环引用详解的更多相关文章
- iOS-block循环引用详解和应用
Block循环引用 什么情况下block会造成循环引用 ARC 情况下 block为了保证代码块内部对象不被提前释放,会对block中的对象进行强引用,就相当于持有了其中的对象,而如果此时block中 ...
- 批处理命令 For循环命令详解!
批处理for命令详解FOR这条命令基本上都被用来处理文本,但还有其他一些好用的功能!看看他的基本格式(这里我引用的是批处理中的格式,直接在命令行只需要一个%号)FOR 参数 %%变量名 IN (相关文 ...
- 【转】批处理命令 For循环命令详解!
批处理for命令详解FOR这条命令基本上都被用来处理文本,但还有其他一些好用的功能!看看他的基本格式(这里我引用的是批处理中的格式,直接在命令行只需要一个%号)FOR 参数 %%变量名 IN (相关文 ...
- php中关于引用(&)详解
php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...
- ios - block循环引用Demo示例
当实例变量中有了block属性,并且用copy来修饰,但是当调用block中的代码的时候,如果block中运用了self.属性的时候回造成循环引用. // // ViewController.h // ...
- iOS Block循环引用
在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...
- IOS block 循环引用的解决
在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...
- C++引用(&)详解
C++引用详解 引用的概念 引用:就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样. 引用的声明方法:类型标识符 &引用名=目标变量名: 如下:定义引用ra,它是变量a的引 ...
- C++11 左值、右值、右值引用详解
C++11 左值.右值.右值引用详解 左值.右值 在C++11中所有的值必属于左值.右值两者之一,右值又可以细分为纯右值.将亡值. 在C++11中可以取地址的.有名字的就是左值,反之,不能取地址的.没 ...
随机推荐
- Spring WebFlux快速上手——响应式Spring的道法术器
https://blog.csdn.net/get_set/article/details/79480233
- leetcode1047
思路分析,这题是在栈分类的题目的,所以顺便复习下数据结构,先用java现成的,就当复习了. 1.判断栈顶和插入的元素是否相等,如果相等,那就出栈,不相等入栈结束 java版:
- ROS2学习之旅(21)——创建一个动作服务和客户节点(C++)
动作是ROS中的一种异步通信形式,动作客户端向动作服务器发送目标请求,目标服务器向操作客户端发送目标反馈和结果.本文基于前一篇自定义动作博文. 1.创建一个action_turtorials_cpp包 ...
- 教你 PXE高效批量网络装机
PXE高效批量网络装机一.PXE概述① PXE (Preboot eXcution Environment)② PXE批量部署的优点③ 服务端④ 客户端二.部署PXE远程安装服务搭建PXE远程安装服务 ...
- C语言:2.2.1-4
#include <stdio.h> #define PI 3.1415926 //宏定义末尾没有分别.如果有则成为字符串的一部分 int main() { printf("显示 ...
- python exec()函数
''' 函数的作用: 动态执行python代码.也就是说exec可以执行复杂的python代码,而不像eval函数那样只能计算一个表达式的值. exec(source, globals=None, l ...
- Qt5MV自定义模型与实例浅析
1. Model/View结构 这种结构,其实就是将界面组件与所编辑的数据分离开来,又通过数据源的方式连接起来,相当于解耦,视图层只关心显示和与用户交互,而数据层负责与实际的数据进行通信,并为视图组件 ...
- Python Unittest简明教程
1 概述 单元测试框架是一种软件测试方法,通过来测试源代码中的各个单元,例如类,方法等,以确定它们是否符合要求.直观上来说,可以将单元视为最小的可测试部分.单元测试是程序员在开发过程中创建的短代码片段 ...
- 【洛谷P5008 逛庭院】tarjan缩点+贪心
既然没有题解,那么我就来提供给一份. -- 首先我们看到数据范围.妈耶!数据这么大,一开始还想用个DP来做,但是看着就不行,那么根据这个数据范围,我们大致可以猜到这道题的算法是一个贪心,那么我们怎么贪 ...
- 在Windows7/8/10 64位操作系统下安装并注册ocx控件
例如: 先网上下载一个MtbLine.ocx控件放入C:\Windows\SysWOW64\目录下 1.首先确保你的 Windows7 账户是管理员权限 2.下载MtbLine.ocx控件,网上可搜到 ...