前言前段时间因为打算换工作,本来以为我已经复习好做好面试的准备了,结果约了一场面试,面试官问我了这个问题,我竟然回答得不知所措,所以赶紧学习脑补一下
关于block为什么要使用copy修复符,首先我们要了解一下关于栈区和堆区的概念
内存的栈区:由编译器自动分配和释放,存放函数的参数值,局部变量的值等,其操作类似数据结构的栈。
内存的堆区:一般由程序员分配和释放,若程序员不释放,程序结束时可能有OS进行回收,注意这里的内存堆区与数据结构中的堆是两回事,分配方式倒是类似于链表。
在iOS中block的类型想必很多开发人员知道一般用copy修饰block,但是为什么要使用copy来修饰呢,其实在Objective-C语言中,一共有3中类型的block:
_NSConcreteGlobalBlock 全局的静态 block,不会访问外部局部变量(显然包括无外部变量或者全局变量)
_NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
_NSConcreteMallocBlock 保存在堆中的 block,当应用计数为 0 时。
测试代码代码(摘自网络):
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869#import "ViewController.h"@interface ViewController ()@property (nonatomic,copy)void(^demoBolck)(); @property (nonatomic,strong)void(^demoBolck1)();@end@implementation ViewControllerint b=8;//全局变量- (void)viewDidLoad { [super viewDidLoad]; void (^demoBolck)() = ^{ NSLog(@"indemoBolck"); }; NSLog(@"demoBolck %@",demoBolck); //<__NSGlobalBlock__: 0x1085af0e0> 无论ARC还是MRC下,因不访问外部局部(包括无外部变量或者只有全局变量),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
注意:测试代码需要把创建的类对象改成MRC才能打印到对应的数据 -fno-objc-arc
关于为什么block是copy来修饰,这涉及到block作用域的问题首先,block是一个对象,所以block理论上是可以retain/release的,但是block在创建的时候它的内存默认是分配在栈(stack)上,而不是堆(heap)上的。所以它的作用域仅限创建时候的当前上下文(函数,方法,…),当你在该作用域外调用该block时,程序就会崩溃,所以为什么block需要使用copy来修饰,其实目的就是为了把它从内存的栈上挪动到内存的堆上,把其作用域扩大。
官方文档:
总结
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变量指向weaSelf?原因:block会把写在block里的变量copy一份,如果直接在block里使用self,(self对变量默认是强引用)self对block持有,导致循环引用,所以这里需要声明一个弱引用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会正确释放。
注意
一般情况下你不需要自行调用copy或者retain一个block,只有当你需要在block定义域以外的地方使用时才需要copy,copy将block从内存栈区移到堆区。
其实block使用copy是MRC留下来的也算是一个传统吧,在MRC下,如上述,在方法中的block创建在栈区,使用copy就能把他放到堆区,这样在作用域外调用block程序就不会奔溃。
在ARC下,使用copy与strong其实都一样,因为block的retain就是用copy来实现的,所以block使用copy还能装装逼,所以自己是从MRC下走过来的。