一,首先介绍什么是Block?它就是一个(里面存储了 指向函数体中包含定义block时的代码块的函数指针,以及block外部上下文变量等信息的)结构体。
二,在OC中,有三种类型的Block:
1,_NSConcreteGlobalBlock 保存在全局区
2,_NSConcreteStackBlock 保存在栈中
3,_NSConcreteMallocBlock 保存在堆中
在代码中,具体怎么判断属于哪种类型呢?请看下面demo
提示:该demo是在MRC的环境下运行的。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 一
NSInteger i = 10;
void(^oneBlock)(void) = ^{
NSLog(@"%zd", i);
};
NSLog(@"%@", oneBlock);
//打印结果:__NSStackBlock__
//二
void(^twoBlock)(void) = ^{
};
NSLog(@"%@",twoBlock);
//打印结果:__NSGlobalBlock__
//三
void(^threeBlock)(void) = [oneBlock copy];
NSLog(@"%@",threeBlock);
//打印结果:__NSMallocBlock__
}
@end
log信息:
2017-08-17 09:47:08.514 test[1008:27892] <__NSStackBlock__: 0xbff12f38>
2017-08-17 09:47:08.515 test[1008:27892] <__NSGlobalBlock__: 0xee060>
2017-08-17 09:47:08.515 test[1008:27892] <__NSMallocBlock__: 0x79789a30>
结果分析:
1,oneBlock使用了外部变量,地址显示在栈区(MRC)。
2,twoBlock没有使用外部变量,地址显示在全局区(ARC和MRC一样)。
由1和2得出结论,如果block没有访问外部变量,那么该block是NSGlobalBlock,如果访问了外部变量,那么该block是NSStackBlock。
3,在对栈区oneBlock进行copy之后,得到的threeBlock地址显示在堆区。
由3得出结论:对栈区block进行copy之后的得到的新block在堆区。
其实在使用__block变量的Block从栈上复制到堆上时,__block变量也被从栈复制到堆上并被Block所持有。
三,变量的复制
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的,如果对象是引用类型,则block会将其引用计数加一 ,如下图所示:
对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的,如下图所示:
四,ARC对block的影响
在ARC模式下,在栈间传递block时,不需要手动copy栈中的block,即可让block正常工作。主要原因是ARC对栈中的block自动执行了copy,将_NSConcreteStackBlock类型的block转换成了_NSConcreteMallocBlock的block。
@interface ViewController : UIViewController
@property (strong,nonatomic)void (^block)();
@property (weak,nonatomic)void (^weakBlock)();
@property (strong,nonatomic)void (^stackBlock)();
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//
NSInteger i = 10;
self.block = ^{
NSLog(@"%zd", i);
};
NSLog(@"%@", self.block);
//
self.weakBlock = ^{
NSLog(@"%zd", i);
};
NSLog(@"%@", self.weakBlock);
//
self.stackBlock = ^{};
NSLog(@"%@", self.stackBlock);
}
打印结果:
2017-08-17 13:24:32.088 test[1882:89514] <__NSMallocBlock__: 0x7ba2a200>
2017-08-17 13:24:32.088 test[1882:89514] <__NSStackBlock__: 0xbff3ef20>
2017-08-17 13:24:32.089 test[1882:89514] <__NSGlobalBlock__: 0xc2080>
结论:
1,ARC会自动帮strong类型且捕获外部变量的block进行copy。
2,ARC不会帮weak类型且捕获外部变量的block进行copy。
此时weakBlock是在栈内存上,由于栈内存有生命周期的约束,所以在运行到touchesBegan方法的时候,weakBlock已经被释放了,程序会奔溃。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.weakBlock();
}
重点:研究block属性用strong,weak,还是copy修饰的目的,就是保证block在使用的时候不会出现被释放的情况。
这里还有一点关于block类型的ARC属性。上文也说明了,ARC会自动帮strong类型且捕获外部变量的block进行copy,所以在定义block类型的属性时也可以使用strong,不一定使用copy。也就是以下代码:
/** 假如有栈block赋给以下两个属性 **/
// 这里因为ARC,当栈block中会捕获外部变量时,这个block会被copy进堆中
// 如果没有捕获外部变量,这个block会变为全局类型
// 不管怎么样,它都脱离了栈生命周期的约束
// 这里都会被copy进堆中
@property (strong, nonatomic) Block *strongBlock;
@property (copy, nonatomic) Block *copyBlock;
五:在开发中的应用场景
1,block作为类的属性,在需要的时候去调用。
2,block作为方法的参数使用,在函数的内部去调用,由外界去定义。
3,block作为方法的返回值使用,在函数的内部去定义,由外界去调用,目的是替代方法。
项目中注意事项:1,循环引用的问题。2,声明block属性的修饰符问题。3,block访问外部变量的问题。
参考文章:www.cocoachina.com/ios/20161025/17198.html