Block循环引用

什么情况下block会造成循环引用

ARC 情况下 block为了保证代码块内部对象不被提前释放,会对block中的对象进行强引用,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,就会造成循环引用。

常见误区

误区一.所有block都会造成循环引用

在block中,并不是所有的block都会循造成环引用,比如UIView动画block、Masonry添加约束block、AFN网络请求回调block等。    

1. UIView动画block不会造成循环引用是因为这是类方法,不可能强引用一个类,所以不会造成循环引用。    

2. Masonry约束block不会造成循环引用是因为self并没有持有block,所以我们使用Masonry的时候不需要担心循环引用。

  • Masonry内部代码
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
//这里并不是self.block (self并没有持有block 所以不会引起循环引用)
block(constraintMaker);
return [constraintMaker install];
}

3.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];
}
 

误区二.block中只有self会造成循环引用

在block中并不只是self会造成循环引用,用下划线调用属性(如_name)也会出现循环引用,效果和使用self是一样的(内部会用self->name去查找)。

//会造成循环引用
_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"张三"; [_person1 Block:^{
NSLog(@"%@",_person2.name)
}];

误区三.通过__weak __typeof(self) weakSelf = self;可以解决所有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);
});
}];
 

误区四.用self调用带有block的方法会引起循环引用

并不是所有通过self调用带有block的方法会引起循环引用,需要看方法内部有没有持有self。

//不会引起循环引用
[self dismissViewControllerAnimated:YES completion:^{
NSLog(@"%@",self.string);
}];
 

如何避免循环引用

方式一、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);
});
}];
 

方式二、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);
}]; }
 

方式三.将对象置为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是否会引起循环引用,如果会引起循环引用,再考虑采取哪种方式来避免循环引用。

iOS-block循环引用详解和应用的更多相关文章

  1. Block循环引用详解

    前言 在项目中经常用到block,使用不当就很容易因为循环引用而造成内存泄漏.本文分析了block循环引用形成原因以及处理办法,如果有什么不对或者疑问请留言. 什么情况下block会造成循环引用 bl ...

  2. ios - block循环引用Demo示例

    当实例变量中有了block属性,并且用copy来修饰,但是当调用block中的代码的时候,如果block中运用了self.属性的时候回造成循环引用. // // ViewController.h // ...

  3. iOS Block循环引用

    在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...

  4. IOS block 循环引用的解决

    在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...

  5. ios block循环引用问题

    ios开发中,开了ARC模式,系统自动管理内存,如果程序中用到了block就要注意循环引用带来的内存泄露问题了 这几天遇到一个问题,正常页面dismiss的时候是要调用dealloc方法的,但是我的程 ...

  6. iOS中Block循环引用的问题

    说到循环引用问题,想必大家都碰到过吧,比如在使用Block的时候,使用__weakSelf来代替self解决等,但是对于这个,还是有不少可以探索的点,下面我就来说下,希望对大家有所帮助. 是否所有的B ...

  7. ios新特征 ARC详解

    IOS ARC 分类: IOS ARC2013-01-17 09:16 2069人阅读 评论(0) 收藏 举报   目录(?)[+]   关闭工程的ARC(Automatic Reference Co ...

  8. 批处理命令 For循环命令详解!

    批处理for命令详解FOR这条命令基本上都被用来处理文本,但还有其他一些好用的功能!看看他的基本格式(这里我引用的是批处理中的格式,直接在命令行只需要一个%号)FOR 参数 %%变量名 IN (相关文 ...

  9. 【转】批处理命令 For循环命令详解!

    批处理for命令详解FOR这条命令基本上都被用来处理文本,但还有其他一些好用的功能!看看他的基本格式(这里我引用的是批处理中的格式,直接在命令行只需要一个%号)FOR 参数 %%变量名 IN (相关文 ...

随机推荐

  1. linux 开机自启动的两种方式

    方法 1 – 使用 rc.local sudo vi /etc/rc.local 在文件最后加上: sh /root/script.sh & 如果是 CentOS,我们修改的是文件 /etc/ ...

  2. 在docker的镜像中安装vim

    在使用docker容器时,有时候里边没有安装vim,敲vim命令时提示说:vim: command not found,这个时候就需要安装vim,可是当你敲apt-get install vim命令时 ...

  3. ollvm混淆的某apk题目的逆向分析

    打开jadx,就发现了我们的老朋友数字壳 典型的类抽取壳,直接上fart脱就完事了,我这里使用的是fart的frida脚本,省去了刷机的步骤 这里的脱壳脚本,自行去github的寒冰大佬那边clone ...

  4. ROS2学习之旅(15)——编写简单的服务和客户节点(C++)

    当节点使用服务进行通信时,发送数据请求的节点称为客户节点,响应请求的节点称为服务节点.请求和响应的结构由.srv文件决定. 本文的例子是一个简单的整数加法系统:一个节点请求两个整数的和,另一个节点响应 ...

  5. ESP32-http client笔记

    基于ESP-IDF4.1 #include <string.h> #include <stdlib.h> #include "freertos/FreeRTOS.h& ...

  6. win10 sql2008r2网页不能使用数据,需要开启端口1433

    1.打开sql server configuratiton Manager 2.sqlserver网络配置--SQLEXPRESS的协议:都启用 3.双击TCP/IP:选择"IP地址&quo ...

  7. EasyUI学后总结第一集

    1,创建Easyui组件-使用html还是使用js方式? 如果在创建Easyui组件的时候,组件再被更改,那么属于静态组件,对于静态组件不要使用js方式创建--会增加js代码量. 如果创建的Easyu ...

  8. 数组去重汇总—v客学院技术分享

    上周基础班结束了数组的学习内容,这几天有时间整理了下几种比较常用的数组去重的方法供大家查阅!!!!! 话不多说,直接贴代码吧~~~~~~~ 欢迎大家指正,共同学习,一同进步!!! (php开发,web ...

  9. Python语言对Json对象进行新增替换操作

    # Json字符串进行新增操作import jsonimport os# os.path.dirname(__file__):表示当前目录path = os.path.join(os.path.dir ...

  10. springMVC-7-数据处理转换

    我们为什么要对数据进行处理? 需求:在上个crud中我们如果需要每次修改的时候都要把时间也记录下来 解决:在jsp中新增一个input,在employee中新增一个Data字段 问题:input输出来 ...