我在 Objective-C中Block的类型 一文中说到MRC下有NSGlobalBlock、NSMallocBlock以及NSStackBlock三种类型的block,而ARC下是没有NSStackBlock类型的,在这里对其进行一个更正补充。
在RAC下,进行如下测试:
CGFloat f = 1.1;
NSLog(@"%@", ^{NSLog(@"%lf",f);});
NSLog(@"%@",[^{NSLog(@"%lf",f);} copy]);
void(^deliveryBlock)(void) = ^{NSLog(@"%lf",f);};
NSLog(@"%@", deliveryBlock);
输出日志为
2017-03-24 22:20:22.526 testdemo[48961:588668] <__NSStackBlock__: 0x7fff525a8c20>
2017-03-24 22:20:22.526 testdemo[48961:588668] <__NSMallocBlock__: 0x60000005e420>
2017-03-24 22:20:22.527 testdemo[48961:588668] <__NSMallocBlock__: 0x60000005e420>
我发现,在直接打印block的时候,他的类型显示的还是NSStackBlock。而我们将这个block进行赋值之后,打印deliveryBlock的结果是NSMallocBlock类型。
也就是说,NSStackBlock类型在ARC下是存在,只是在对他进行赋值的时候,编译器将栈区的block拷贝到了堆区(赋值和copy在这里效果相同)。
至于原因
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *obj = [[NSObject alloc]init];
void(^deliveryBlock)(void) = ^{
NSLog(@"%@",obj);
};
[self didBlock:deliveryBlock];
}
- (void)didBlock:(void(^)(void)) block {
NSLog(@"%@",block);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
block();
});
}
参照上方代码,假如deliveryBlock是NSStackBlock类型,如果我们把deliveryBlock当作方法参数传递到了另外一个方法,那么一旦deliveryBlock不在原来方法的调用栈,而新方法调用deliveryBlock的时机又不得而知,可能在新方法调用的时候deliveryBlock的时候堆区的obj已经被释放。即:ARC下编译器在NSStackBlock类型的block传递过程中进行了自动优化。
为了进一步验证,我做了如下测试
- (void)viewDidLoad {
[super viewDidLoad];
[self test:^{
NSLog(@"%@",self.object);
}];
}
- (void)test:(void(^)(void))block {
NSLog(@"%@",block); //log: <__NSStackBlock__: 0x7fff5ef49b88>
}
当object作为self的属性时,object在整个vc的生命周期存在,故不会有脱离调用栈的问题,所以编译器没有将其自动拷贝到堆。