想必很多开发人员知道一般用copy修饰block,接下来就讲解为什么需要用copy,甚至会讲到其实用strong修饰block也是可以的
在 Objective-C 语言中,一共有 3 种类型的 block:
- _NSConcreteGlobalBlock 全局的静态 block,没有访问外部局部变量(基本数据、OC对象)、成员属性变量或只用到全局变量、静态变量(局部或者全局静态变量)。
- _NSConcreteStackBlock 保存在栈中的 block,只用到外部局部变量(基本数据、OC对象)、成员属性变量,且没有强指针引用的block。当函数返回时会被销毁。
- _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会正确的释放。
原因: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会正确的释放。