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中可以取地址的.有名字的就是左值,反之,不能取地址的.没 ...
随机推荐
- Springboot:@RequestMapping注解及属性详解
@RequestMapping 注解: @RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一.这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理 ...
- php mkdir 创建多级目录以及修改权限
mkdir() 用法:mkdir($path,0777,true); 第一个参数:必须,代表要创建的多级目录的路径:第二个参数:设定目录的权限,默认是 0777,意味着最大可能的访问权:注意:mode ...
- Game游戏分析
1.鲁棒图分析 2.系统上下文及交互方式 3.用例 4.逻辑拓扑图 5.物理拓扑图 6.时序图 7.状态图 8.物理数据模型 9.类图 10.技术选型 11.框架搭建 12.工具及通用服务 13.架构 ...
- Android Binder 进程间通讯机制梳理
什么是 Binder ? Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一.Binder的设计采用了面向对象的思想,在Binder通信模型的四 ...
- Vue3 + Cesium + Typescript 集成搭建的快速启动模板(包含示例数据)
开门见山 项目地址:https://github.com/tanghaojie/vue3-cesium-typescript-start-up-template 好用的话给个star呗,有更新可以第一 ...
- F5负载均衡-配置指导手册(含IPv6)
F5负载均衡-配置手册 设备概况 图形化界面 通过网络形式访问F5任一接口地址,打开浏览器输入https://网络接口地址:或pc机直连F5的MGMT带外管理口,打开浏览器,输入https://192 ...
- ASP.NET C# 打包再修改aspx文件报错解决方案
aspx文件最开始: <%@ page language="C#" autoeventwireup="true" inherits="tiddk ...
- java测试银行系统源代码
1 package Kaoshi; 2 3 /*信1705-3 20173442 田昕可*/ 4 import java.util.*; 5 import java.io.*; 6 7 class A ...
- 在deeping上安装mariadb
1,安装的官网参考:有安装的命令和指导https://downloads.mariadb.org/mariadb/repositories/#distro=Debian&distro_rele ...
- ubuntu18.04aliyun
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb-src http://mirr ...