前言: 前面学习了那么多block的知识, 其实就为了解决项目中的几个问题
1. ARC 与 MRC下__block的区别
2. __block 和 __weak的区别
3. block内嵌的注意事项
4. block使用场景中的block块中, 引用self是否使用__weak或__block
1. ARC 与 MRC下__block的区别
- 在 MRC 下,使用 __block 说明符也可以避免循环引用。因为当 block 从栈拷贝到堆时,__block 对象类型的变量不会被 retain,没有 __block 说明符的对象类型的变量则会被 retian
- 在ARC下, 使用__block会retain修饰的对象类型, 防止循环引用可使用__weak(iOS5.0才有)或__unsafe_unretain
2. __block 和 __weak的区别
__weak specifies a reference that does not keep the
referenced object alive. A weak reference is set to nil when
there are no strong references to the object.
使用了__weak修饰符的对象,作用等同于定义为weak的property。自然不会导致循环引用问题,因为苹果文档已经说的很清楚,当原对象没有任何强引用的时候,弱引用指针也会被设置为nil。
因此,__block和__weak修饰符的区别其实是挺明显的:
- ARC下__weak修饰的对象在改对象销毁后也会被销毁, 并置为nil, 但是__block修饰的对象在该对象销毁后仍然存在, 所以容易造成循环引用
- __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
- __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
- __block对象可以在block中被重新赋值,__weak不可以。
- __block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。
PS:__unsafe_unretained
修饰符可以被视为iOS SDK 4.3以前版本的__weak的替代品,不过不会被自动置空为nil。所以尽可能不要使用这个修饰符。
3. block内嵌block的注意事项
在block里面使用block,比如在GCD的block里内嵌另一个block。因为GCD本身会拷贝传入dispatch_queue中的block,内嵌的block也会同时被拷贝:
When you copy a block, any references to other blocks from within that block are copied if necessary—an entire tree may be copied (from the top). If you have block variables and you reference a block from within the block, that block will be copied.
另外当block作为NSDictionary的key时,这些key都是默认copy的。原因很简单:NSDictionary是通过key hash拿到的值进行查询,如果不对key进行拷贝,则在进行查询时会因为key的变化而导致查询失败。(所以也不太推荐拿一些复杂易变的对象当作NSDictionary的key)
4. block使用场景中的block块中, 引用self是否使用__weak或__block
AFN GCD Masonry等
将Block作为参数传给AFN, dispatch_async, Masonry时,系统会将Block拷贝到堆上,如果Block中使用了实例变量,还将retain self,因为AFN, dispatch_async, Masonry并不知道self会在什么时候被释放,为了确保系统调度执行Block中的任务时self没有被意外释放掉,AFN, dispatch_async, Masonry必须自己retain一次self,任务完成后再release self。但这里使用__weak,使AFN, dispatch_async, Masonry没有增加self的引用计数,这使得在系统在调度执行Block之前,self可能已被销毁,但系统并不知道这个情况,导致Block被调度执行时self已经被释放导致crash。
//#import "Person.h"
- (void)say {
__weak typeof(self) weakSelf = self;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"%@", weakSelf);
});
}
//#import "ViewController.h"
//#import "Person.h"
Person *person = [[Person alloc] init];
[person say];
这里用dispatch_after模拟了一个异步任务,10秒后执行Block。但执行Block的时候Person *person已经被释放了,打印为
2016-03-15 15:14:16.048 TestBlock[3588:143295] (null)
,
导致crash。解决办法是不要使用__weak
控制器间
…
5. Block实例注意
使用__block与不使用__block的比较:
typedef long (^BlkSum)(int, int);
int base = 100;
BlkSum sum = ^ long (int a, int b) {
return base + a + b;
};
base++;
printf("%ld\n",sum(1,2)); // result: 103
printf("%d\n",base); // result: 101
typedef long (^BlkSum)(int, int);
__block int base = 100;
BlkSum sum = ^ long (int a, int b) {
return base + a + b;
};
base++;
printf("%ld\n",sum(1,2)); // result: 104
printf("%d\n",base); // result: 101
- 使用__block修饰变量时,将取变量
此刻运行时的值
,而不是定义时的值。这个例子中,执行sum(1,2)时,base将取base++之后的值,也就是101,再执行 base+a+b,运行结果是104。执行完Block时,base已经变成101了。- 不使用__block修饰变量时,将取变量定义时的值。这个例子中,执行sum(1,2)时是100,再执行 base+a+b,运行结果是103。
在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和NSConcreteMallocBlock类型的 block。
原本的 NSConcreteStackBlock 的 block 会被 NSConcreteMallocBlock 类型的 block 替代, 因此使用strong和copy修饰block属性区别不大, 但是官方还是建议使用copy修饰