前言

在项目中经常用到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内部代码
  1. - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
  2.     self.translatesAutoresizingMaskIntoConstraints = NO;
  3.     MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
  4. //这里并不是self.block
  5.     block(constraintMaker);
  6.     return [constraintMaker install];
  7. }

AFN请求回调block不会造成循环引用是因为在内部做了处理。block先是被AFURLSessionManagerTaskDelegate对象持有。而AFURLSessionManagerTaskDelegate对象被mutableTaskDelegatesKeyedByTaskIdentifier字典持有,在block执行完成后,mutableTaskDelegatesKeyedByTaskIdentifier字典会移除AFURLSessionManagerTaskDelegate对象,这样对象就被释放了,所以不会造成循环引用。

  • AFN内部代码
  1. #pragma mark - 添加代理
  2. - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
  3.                 uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
  4.               downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
  5.              completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
  6. {
  7.     AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
  8.     delegate.manager = self;
  9.      //block被代理引用
  10.     delegate.completionHandler = completionHandler;
  11.     dataTask.taskDescription = self.taskDescriptionForSessionTasks;
  12.     //设置代理
  13.     [self setDelegate:delegate forTask:dataTask];
  14.     delegate.uploadProgressBlock = uploadProgressBlock;
  15.     delegate.downloadProgressBlock = downloadProgressBlock;
  16. }
  17. #pragma mark - 设置代理
  18. - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
  19.             forTask:(NSURLSessionTask *)task
  20. {
  21.     NSParameterAssert(task);
  22.     NSParameterAssert(delegate);
  23.     [self.lock lock];
  24.     //代理被mutableTaskDelegatesKeyedByTaskIdentifier字典引用
  25.     self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
  26.     [delegate setupProgressForTask:task];
  27.     [self addNotificationObserverForTask:task];
  28.     [self.lock unlock];
  29. }
  30. #pragma mark - 任务完成
  31. - (void)URLSession:(NSURLSession *)session
  32.           dataTask:(NSURLSessionDataTask *)dataTask
  33. didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
  34. {
  35.     AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
  36.     if (delegate) {
  37.         //任务完成,移除
  38.         [self removeDelegateForTask:dataTask];
  39.         [self setDelegate:delegate forTask:downloadTask];
  40.     }
  41.     if (self.dataTaskDidBecomeDownloadTask) {
  42.         self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
  43.     }
  44. }
  45. #pragma mark - 移除任务代理
  46. - (void)removeDelegateForTask:(NSURLSessionTask *)task {
  47.     NSParameterAssert(task);
  48.     AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
  49.     [self.lock lock];
  50.     [delegate cleanUpProgressForTask:task];
  51.     [self removeNotificationObserverForTask:task];
  52.     //移除
  53.     [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
  54.     [self.lock unlock];
  55. }

2.block中只有self会造成循环引用

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

  1. //会造成循环引用
  2. _person1 = [[Person alloc] init];
  3. _person2 = [[Person alloc] init];
  4. _person2.name = @"张三";
  5.     
  6. _person1.block = ^{
  7.     NSLog(@"%@",_person2.name)
  8. };

3.weakSelf可以解决所有block造成的循环引用

大部分情况下,这样使用是可以解决block循环引用,但是有些情况下这样使用会造成一些问题,比如在block中延迟执行一些代码,在还没有执行的时候,控制器被销毁了,这样控制器中的对象也会被释放,__weak对象就会变成null。所以会输出null。

  1. //在延迟执行期间,控制器被释放了,打印出来的会是**(null)**
  2. _person1 = [[Person alloc] init];
  3. _person2 = [[Person alloc] init];
  4. _person2.name = @"张三";
  5. __weak __typeof(self) weakSelf = self;
  6. _person1.block = ^{
  7.     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  8.         NSLog(@"%@",weakSelf.person2.name);
  9.     });
  10. };

4.用self调用带有block的方法会引起循环引用

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

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

5.类方法不会造成循环引用

这是一种错误的说法,类方法我们需要知道其内部是否持有对象,如果内部持有对象,并且在block执行后未释放持有的对象,那么也会造成循环引用。

如何避免循环引用

1.weakSelf、strongSelf结合使用

使用weakSelf结合strongSelf的情况下,能够避免循环引用,也不会造成提前释放导致block内部代码无效。

  1. _person1 = [[Person alloc] init];
  2. _person2 = [[Person alloc] init];
  3. _person2.name = @"张三";
  4. __weak __typeof(self) weakSelf = self;
  5. _person1.block = ^{
  6.     __typeof(&*weakSelf) strongSelf = weakSelf;
  7.     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  8.         NSLog(@"%@",strongSelf.person2.name);
  9.     });
  10. };

2.block的外部对象使用week

外部对象通过week修饰,使用全局弱指针指向一个局部强引用对象,这样局部变量在超出其作用域后也不会被销毁,因为是弱指针,所以不会造成循环引用。

  1. @interface CLViewController ()
  2. //弱引用指针
  3. @property (nonatomic,weak) Person *person1;
  4. @property (nonatomic,strong) Person *person2;
  5. @end
  6. @implementation CLViewController
  7. - (void)viewDidLoad {
  8.     [super viewDidLoad];
  9.     self.view.backgroundColor = [UIColor redColor];
  10.     //局部强引用对象
  11.     Person *person1 = [[Person alloc] init];
  12.     _person1 = person1;
  13.     
  14.     
  15.     _person2 = [[Person alloc] init];
  16.     _person2.name = @"张三";
  17.     _person1.block = ^{
  18.             NSLog(@"%@",self.person2.name);
  19.     };
  20. }

3.将对象置为nil

使用完对象之后就没有必要再保留该对象了,将对象置为nil。在ARC中,被置为nil的对象会被销毁。虽然这样也可以达到破除循环引用的效果,但是这样使用起来很不方便,如果一个对象有多个block,在前面将对象置空,后面的block就不会执行,所以使用这种方法来防止循环引用,需要注意在合适的位置将对象置空。

  1. _person1 = [[Person alloc] init];
  2. _person2 = [[Person alloc] init];
  3. _person2.name = @"张三";
  4. _person1.block = ^{
  5.     NSLog(@"%@",self.person2.name);
  6.     //置空,避免循环引用
  7.     _person1 = nil;
  8. };
  9. //由于上面已经将对象置空,所以这里block里边的代码不会执行
  10. _person1.block = ^{
  11.     NSLog(@"%@",self.person2.name);
  12. };

虽然这种方式使用起来不是很友好,但是在封装block的时候,可以考虑使用完马上置空当前使用的block,这样使用的时候就不需要考虑循环引用的问题。

  1. - (void)back
  2. {
  3.     if (self.BackBlock)
  4.     {
  5.         self.BackBlock(button);
  6.     }
  7. //使用完,马上置空当前block
  8.     self.BackBlock = nil;
  9. }

总结

循环引用的本质是当前对象对这个block进行了强持有,并且block内部对这个对象进行了强持有。使用block的时候,我们首先需要做的就是判断当前block是否会引起循环引用,如果会引起循环引用,再考虑采取哪种方式来避免循环引用。

Block循环引用详解的更多相关文章

  1. iOS-block循环引用详解和应用

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

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

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

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

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

  4. php中关于引用(&)详解

    php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...

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

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

  6. iOS Block循环引用

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

  7. IOS block 循环引用的解决

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

  8. C++引用(&)详解

    C++引用详解 引用的概念 引用:就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样. 引用的声明方法:类型标识符 &引用名=目标变量名: 如下:定义引用ra,它是变量a的引 ...

  9. C++11 左值、右值、右值引用详解

    C++11 左值.右值.右值引用详解 左值.右值 在C++11中所有的值必属于左值.右值两者之一,右值又可以细分为纯右值.将亡值. 在C++11中可以取地址的.有名字的就是左值,反之,不能取地址的.没 ...

随机推荐

  1. Springboot:@RequestMapping注解及属性详解

    @RequestMapping 注解: @RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一.这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理 ...

  2. php mkdir 创建多级目录以及修改权限

    mkdir() 用法:mkdir($path,0777,true); 第一个参数:必须,代表要创建的多级目录的路径:第二个参数:设定目录的权限,默认是 0777,意味着最大可能的访问权:注意:mode ...

  3. Game游戏分析

    1.鲁棒图分析 2.系统上下文及交互方式 3.用例 4.逻辑拓扑图 5.物理拓扑图 6.时序图 7.状态图 8.物理数据模型 9.类图 10.技术选型 11.框架搭建 12.工具及通用服务 13.架构 ...

  4. Android Binder 进程间通讯机制梳理

    什么是 Binder ? Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一.Binder的设计采用了面向对象的思想,在Binder通信模型的四 ...

  5. Vue3 + Cesium + Typescript 集成搭建的快速启动模板(包含示例数据)

    开门见山 项目地址:https://github.com/tanghaojie/vue3-cesium-typescript-start-up-template 好用的话给个star呗,有更新可以第一 ...

  6. F5负载均衡-配置指导手册(含IPv6)

    F5负载均衡-配置手册 设备概况 图形化界面 通过网络形式访问F5任一接口地址,打开浏览器输入https://网络接口地址:或pc机直连F5的MGMT带外管理口,打开浏览器,输入https://192 ...

  7. ASP.NET C# 打包再修改aspx文件报错解决方案

    aspx文件最开始: <%@ page language="C#" autoeventwireup="true" inherits="tiddk ...

  8. java测试银行系统源代码

    1 package Kaoshi; 2 3 /*信1705-3 20173442 田昕可*/ 4 import java.util.*; 5 import java.io.*; 6 7 class A ...

  9. 在deeping上安装mariadb

    1,安装的官网参考:有安装的命令和指导https://downloads.mariadb.org/mariadb/repositories/#distro=Debian&distro_rele ...

  10. ubuntu18.04aliyun

    deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb-src http://mirr ...