iOS - 深入探究Block循环引用

作者: Mitchell | 来源:发表于2015-10-09 22:48 被阅读4523次
    作者:Mitchell 
    

    一、根据需求提出问题

    • 请耐心把这篇文章看完,你对 Block 会有更深刻的了解。
    • 这里直接用一个需求来探究循环引用的问题:如果我想在Block中延时来运行某段代码,这里就会出现一个问题,看这段代码:
     - (void)viewDidLoad {
        [super viewDidLoad];
        MitPerson*person = [[MitPerson alloc]init];
        __weak MitPerson * weakPerson = person;
        person.mitBlock = ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [weakPerson test];
            });
        };
        person.mitBlock();
    }
    

    直接运行这段代码会发现[weakPerson test];并没有执行,打印一下会发现,weakPerson 已经是 Nil 了,这是由于当我们的 viewDidLoad 方法运行结束,由于是局部变量,无论是 MitPerson 和 weakPerson 都会被释放掉,那么这个时候在 Block 中就无法拿到正真的 person 内容了。

    • 按如下方法修改代码:
     - (void)viewDidLoad {
        [super viewDidLoad];
        MitPerson*person = [[MitPerson alloc]init];
        __weak MitPerson * weakPerson = person;
        person.mitBlock = ^{
            __strong MitPerson * strongPerson = weakPerson;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [strongPerson test];
            });
        };
        person.mitBlock();
    }
    

    这样当2秒过后,计时器依然能够拿到想要的 person 对象。

    二、深入探究原理

    • 这里将会对每行代码逐步进行说明
    1、开辟一段控件存储 person 类对象内容,创建 person 强指针。
     MitPerson*person = [[MitPerson alloc]init];
    
    2、创建一个弱指针 weakPerson 指向person对象内容 
     __weak MitPerson * weakPerson = person;
    
      person.mitBlock = ^{
      3、在 person 对象的 Block 内部创建一个强指针来指向 person 对象,为了保证当计时器执行代码的时候,person 对象没有被系统销毁所以我们必须在系统内部进行一次强引用,并用 GCD 计时器引用 strongPerson,为了保留 person 对象,在下面会对这里更加详细的说明。
        __strong MitPerson * strongPerson = weakPerson;
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
              [strongPerson test];
          });
      };
    
    4、执行 Block 代码
        person.mitBlock();
    
    • 下面将详细分析一下下面这段代码:
      person.mitBlock = ^{
     __strong MitPerson * strongPerson = weakPerson;
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
              [strongPerson test];
          });
      };
    
    • 首先需要明白一些关于 Block 的概念:
      • 1、默认情况下,block 是放在栈里面的
      • 2、一旦blcok进行了copy操作,block的内存就会被放在堆里面
      • 3、堆立面的block(被copy过的block)有以下现象
        • 1> block内部如果通过外面声明的强引用来使用,那么block内部会自动产生一个强引用指向所使用的对象。
        • 2> block内部如果通过外面声明的弱引用来使用,那么block内部会自动产生一个弱引用指向所使用的对象。
    • 我们进行这段代码的目的:
      • 首先,我们需要在 Block 块中调用,person 对象的方法,既然是在 Block 块中我们就应该使用弱指针来引用外部变量,以此来避免循环引用。但是又会出现问题,什么问题呢?就是当我计时器要执行方法的时候,发现对象已经被释放了。
      • 接下来就是为了避免 person 对象在计时器执行的时候被释放掉:那么为什么 person 对象会被释放掉呢?因为无论我们的person强指针还是 weakPerson 弱指针都是局部变量,当执行完ViewDidLoad 的时候,指针会被销毁。对象只有被强指针引用的时候才不会被销毁,而我们如果直接引用外部的强指针对象又会产生循环引用,这个时候我们就用了一个巧妙的代码来完成这个需求。
      • 首先在 person.mitBlock 引用外部 weakPerson,并在内部创建一个强指针去指向 person 对象,因为在内部声明变量,Block 是不会强引用这个对象的,这也就在避免的 person.mitBlock 循环引用风险的同时,又创建出了一个强指针指向对象。
      • 之后再用 GCD 延时器 Block 来引用相对于它来说是外部的变量 strongPerson ,这时延时器 Block 会默认创建出来一个强引用来引用 person 对象,当 person.mitBlock 作用域结束之后 strongPerson 会跟着被销毁,内存中就仅剩下了 延时器 Block 强引用着 person 对象,2秒之后触发 test 方法,GCD Block 内部方法执行完毕之后,延时器和对象都被销毁,这样就完美实现了我们的需求。
    • 最后再用一张图来阐述各个指针、Block 与对象之间的关系
      黑色代表强引用,绿色代表弱引用
      • 总结:person.mitBlock 中创建 strongPerson 是为了能够使 GCD Block 保存 person 对象,创建 strongPerson 时候使用 weakPerson 是为了避免 mitBlock 直接引用外部强指针变量所造成的循环引用。
        Block循环引用.png

    相关文章

      网友评论

      • iOS_小张:2017-09-25 19:55:53.759083+0800 深入探究Block循环引用[73548:1848128] person指针内存地址:5e387b88
        2017-09-25 19:55:53.759255+0800 深入探究Block循环引用[73548:1848128] person指针所指向对象的地址:0x600000018580
        2017-09-25 19:55:53.759340+0800 深入探究Block循环引用[73548:1848128] weakPerson指针内存地址:5e387b80
        2017-09-25 19:55:53.759436+0800 深入探究Block循环引用[73548:1848128] weakPerson指针所指向对象的地址:0x600000018580
        2017-09-25 19:55:53.759532+0800 深入探究Block循环引用[73548:1848128] strongPerson指针内存地址:5e387ac8
        2017-09-25 19:55:53.759644+0800 深入探究Block循环引用[73548:1848128] strongPerson指针所指向对象的地址:0x600000018580
        2017-09-25 19:55:55.956805+0800 深入探究Block循环引用[73548:1848128] test
        2017-09-25 19:55:55.957174+0800 深入探究Block循环引用[73548:1848128] 释放了
        请教一下,为什么person,weakPerson和strongPerson的指针内存地址是一样的?看你的图解,应该是不一样的吧?
      • Easy_VO:1、开辟一段控件存储 person 类对象内容,创建 person 强指针。
        "控件"--->空间-.-
        Easy_VO:讲的的确细致,比较实际的block 套block就是有两处错别字-0-
      • 寂静的天空:基础差,看了5、6遍才看明白 :joy:
      • 逍然:不错,很深入啊,不错如果把MitPerson换成ViewController自身,就不用__strong了。
        code3:@随意奔跑 为什么换成viecontroller就可以不用strong了 没百度出来
      • dongwenbo:值得再多看几遍
      • dongwenbo:谢谢你,很有收获😊
      • 什么的黑夜:大神没怎么看懂,person.mitBlock的作用域这块,麻烦讲解
      • Damonwong:不是所有 block 被 copy都会赋值到堆里面,应该得看 block 的类型吧
        不知什么人:@Damonwong ARC 下基本都是堆block了
        Damonwong:@Mitchell http://blog.oneiv.com/blog/Block-4.html
        Mitchell:@Damonwong 您说的这个block类型指的是???

      本文标题:iOS - 深入探究Block循环引用

      本文链接:https://www.haomeiwen.com/subject/invncttx.html