block的修饰词为什么选用copy

想必很多开发人员知道一般用copy修饰block,接下来就讲解为什么需要用copy,甚至会讲到其实用strong修饰block也是可以的

在 Objective-C 语言中,一共有 3 种类型的 block:

  1. _NSConcreteGlobalBlock 全局的静态 block,没有访问外部局部变量(基本数据、OC对象)、成员属性变量或只用到全局变量、静态变量(局部或者全局静态变量)
  2. _NSConcreteStackBlock 保存在栈中的 block,只用到外部局部变量(基本数据、OC对象)、成员属性变量,且没有强指针引用的block。当函数返回时会被销毁。
  3. _NSConcreteMallocBlock 保存在堆中的 block,有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制。

新建一个项目,分别在ARC环境和MRC环境测试一遍

把测试文件改成MRC环境的方法:

把完整的测试代码显示如下:

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic,copy)void(^demoBolck)();

 @property (nonatomic,strong)void(^demoBolck1)();

@end

@implementation ViewController

int b=8;//全局变量

- (void)viewDidLoad {

    [super viewDidLoad];

    void (^demoBolck)() = ^{

        NSLog(@"indemoBolck");

    }; 

    NSLog(@"demoBolck %@",demoBolck);    //<__NSGlobalBlock__: 0x1085af0e0>  无论ARC还是MRC下,因不访问外部局部变量(基本数据、OC对象)、成员属性变量或只用到全局变量、静态变量(局部或者全局静态变量),NSGlobalBlock表示在全局区

    void (^demoBolck4)() = ^{

        NSLog(@"indemoBolck4  %d",b);

    };

    NSLog(@"demoBolck4 %@",demoBolck4);    //<__NSGlobalBlock__: 0x10150b120>  全局区

    __block int a = 6;   //block内部引用a,并修改其值,需要用block修饰,不然可以不用

    void (^demoBolck2)() = ^{

        NSLog(@"indemoBolck2 %d",a++);

    };

    demoBolck2();

    NSLog(@"demoBolck2 %@,%d",demoBolck2,a);   //<__NSMallocBlock__: 0x600000056c50> ARC下堆区,在ARC模式下,系统也会默认对Block进行copy操作,Block的内存地址这时候便显示在堆区; <__NSStackBlock__: 0x7fff5d0ada28>MRC下在栈区

    NSLog(@"demoBolck2.Copy %@",[demoBolck2 copy]);    //<__NSMallocBlock__: 0x600000056c50>copy操作不管MRC或者ARC都在堆区,只是在MRC下进行copy会改变地址

    self.demoBolck = demoBolck2;

    NSLog(@"self.demoBolck %@",self.demoBolck);//堆区<__NSMallocBlock__: 0x608000052630>

    self.demoBolck1 = demoBolck2;

    self.demoBolck1();     //demoBolck2  7   能执行无问题

    NSLog(@"self.demoBolck1 %@",self.demoBolck1);     //<__NSMallocBlock__: 0x600000056c50>  strong修饰ARC和MRC都并没有问题, 但是assign和retain在MRC环境下是还是在栈区的,会有问题

}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    //注意:MRC环境下:demoBolck1用assign,retain修饰,栈区:<__NSStackBlock__: 0x7fff50915a50>,提前释放了所以运行到下面语句程序会崩溃。ARC环境下:用copy,strong, assign, retain修饰是可以正常打印出结果的。 无论什么环境,用copy,strong修饰是可以正常打印出结果的

    self.demoBolck1();

}

@end

总结:

  • block内部没有调用外部局部变量时存放在全局区(ARC和MRC下均是)
  • block使用了外部局部变量,这种情况也正是我们平时所常用的方式。MRC:Block的内存地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.所以在使用Block属性时使用copy修饰。但是ARC中的Block都会在堆上的,系统会默认对Block进行copy操作
  • 用copy,strong修饰block在ARC和MRC都是可以的,都是在堆区

 

补充:一个block要使用self,会处理成在外部声明一个weak变量指向self,然而为何有时会出现在block里又声明一个strong变量指向weakSelf?
原因:block会把写在block里的变量copy一份,如果直接在block里使用self,(self对变量默认是强引用)self对block持有,block对self持有,导致循环引用,所以这里需要声明一个弱引用weakSelf,让block引用weakSelf,打破循环引用。
而这样会导致另外一个问题,因为weakSelf是对self的弱引用,如果这个时候控制器pop或者其他的方式引用计数为0,就会释放,如果这个block是异步调用而且调用的时候self已经释放了,这个时候weakSelf已就变成了nil。
当控制器(也可以是其他的控件)pop回来之后(或者一些其他的原因导致释放),网络请求完成,如果这个时候需要控制器做出反映,需要strongSelf再对weakSelf强引用一下。
但是,你可能会疑问,strongSelf对weakSelf强引用,weakSelf对self弱引用,最终不也是对self进行了强引用,会导致循环引用吗。不会的,因为strongSelf是在block里面声明的一个指针,当block执行完毕后,strongSelf会释放,这个时候将不再强引用weakSelf,所以self会正确的释放。

 

posted @ 2017-07-10 14:27  菁欣  阅读(4725)  评论(0编辑  收藏  举报