前言

在项目中经常用到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循环引用详解的更多相关文章

  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. Quartz:Quartz任务异常处理方式

    Quartz提供了二种解决方法 1 立即重新执行任务 2 立即停止所有相关这个任务的触发器 解决的方式是:在你的程序出错时,用Quartz提供的JobExecutionException类相关方法就能 ...

  2. linux之软连接 硬链接 link ln

    p.p1 { margin: 0; font: 12px "Helvetica Neue"; color: rgba(220, 161, 13, 1) } p.p2 { margi ...

  3. php curl处理get和post请求

    CURL 是一个利用URL语法规定来传输文件和数据的工具,支持很多协议,如HTTP.FTP.TELNET等.最爽的是,PHP也支持 CURL 库.使用PHP的CURL 库可以简单和有效地去抓网页.你只 ...

  4. linux学习之路第二天(xshell和xftp的使用图解)

    以上xshell的连接过程 2.远程上传和下载文件xftp6 一定要换成22和sftp,否则连接不上 如果出现中文乱码的情况 先点击那个小齿轮 再点击选项 把编码编程UTF-8就ok了

  5. leetcode TOP100 比特位计数

    338. 比特位计数 题目描述: `给定一个非负整数 num.对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回. 示例 1: 输入: 2 输出: ...

  6. 第四章 python的turtle库的运用

    我们可以尝试用python的自带turtle库绘制一条蟒蛇 首先我们设计一下蟒蛇的基本形状 我们先把这段蟒蛇绘制的实例代码贴出来,各位可以在自己的本地运行一下看看效果,然后我们再继续分析代码: 1 # ...

  7. (学习心路历程)Vue过渡/动画 VS. 过渡/动画

    [此篇为本人的个人见解和哔哔赖赖,如果有观点不对的地方,还请大家指出来哇!!] 最近实习在做一个项目,里面应用的动画效果还蛮复杂的,因为本身对Vue框架比较熟悉,所以最终选择了Vue框架. 自己之前从 ...

  8. STM32学习进程

    新建一个自己的工程模板,以我所用的MDK4为例 MDK4软件图标 (1)新建一个自己储存数据的文件夹.以我自己为例(文件夹名字任取自己记住熟悉就行,以下将以我的文件夹文件进行操作讲解) 新建的总体文件 ...

  9. Java 获取Word中指定图片的坐标位置

    本文介绍通过Java程序获取Word文档中指定图片的坐标位置. 程序运行环境: Word测试文档:.docx 2013 Free Spire.doc.jar 3.9.0 IntelliJ IDEA J ...

  10. DNS服务器安全---通过ipset对DNS异常解析流量的源IP地址进行管控

    ipset介绍 ipset是iptables的扩展,它允许你创建 匹配整个地址集合的规则.而不像普通的iptables链只能单IP匹配, ip集合存储在带索引的数据结构中,这种结构即时集合比较大也可以 ...