Block
Block 可以捕获外部变量
Block 可以捕获来自外部作用域的变量,这是Block一个很强大的特性。
- (void)testMethod {
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
testBlock();
}
默认情况下,Block 中捕获的到变量是不能修改的,如果想修改,需要使用__block来声明:
__block int anInteger = 42;
- 对于 id 类型的变量
在 MRC 情况下,使用 __block id x 不会 retain 变量,而在 ARC 情况下则会对变量进行 retain(即和其他捕获的变量相同)。
- 如果不想在 block 中进行 retain 可以使用 __unsafe_unretained __block id x,不过这样可能会导致野指针出现。更好的办法是使用 __weak 的临时变量:
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler = ^(NSInteger result) {
[weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};
- 或者把使用 __block 修饰的变量设为 nil,以打破引用循环:
MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
myController = nil;
};
使用 Block 时的注意事项
在非 ARC 的情况下,对于 block 类型的属性应该使用 copy ,因为 block 需要维持其作用域中捕获的变量。
在 ARC 中编译器会自动对 block 进行 copy 操作,因此使用 strong 或者 copy 都可以,没有什么区别,但是苹果仍然建议使用 copy 来指明编译器的行为。
- block 在捕获外部变量的时候,会保持一个强引用,当在 block 中捕获 self 时,由于对象会对 block 进行 copy,于是便形成了强引用循环:
@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
self.block = ^{
[self doSomething]; // capturing a strong reference to self
// creates a strong reference cycle
};
}
...
@end
- 为了避免强引用循环,最好捕获一个 self 的弱引用:
- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // capture the weak reference
// to avoid the reference cycle
}
}
- 使用弱引用会带来另一个问题,weakSelf 有可能会为 nil,如果多次调用 weakSelf 的方法,有可能在 block 执行过程中 weakSelf 变为 nil。因此需要在 block 中将 weakSelf “强化“
__weak __typeof__(self) weakSelf = self;
NSBlockOperation *op = [[[NSBlockOperation alloc] init] autorelease];
[ op addExecutionBlock:^ {
__strong __typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doMoreThing];
} ];
[someOperationQueue addOperation:op];
- __strong 这一句在执行的时候,如果 WeakSelf 还没有变成 nil,那么就会 retain self,让 self 在 block 执行期间不会变为 nil。这样上面的 doSomething 和 doMoreThing 要么全执行成功,要么全失败,不会出现一个成功一个失败(即执行到中间 self 变成 nil 的情况)。
会变成nil的原因
涉及到 weak 本身的机制了。weak 置 nil 的操作发生在 dealloc 中,最后一个持有 object 的对象被释放的时候,会触发对象的 dealloc,而这个持有者的释放操作就不一定保证发生在哪个线程了。因此 block 执行的过程中 weakSelf 有可能在另外的线程中被置为 nil。
Block 在堆上还是在栈上?
在 MRC 下,Block 默认是分配在栈上的,除非进行显式的 copy:
__block int val = 10;
blk stackBlock = ^{NSLog(@"val = %d", ++val);};
NSLog(@"stackBlock: %@", stackBlock); // stackBlock: <__NSStackBlock__: 0xbfffdb28>
tempBlock = [stackBlock copy];
NSLog(@"tempBlock: %@", tempBlock); // tempBlock: <__NSMallocBlock__: 0x756bf20>
想把 Block 用作返回值的时候,也要加入 copy 和 autorelease:
- (blk)myTestBlock {
__block int val = 10;
blk stackBlock = ^{NSLog(@"val = %d", ++val);};
return [[stackBlock copy] autorelease];
}
在 ARC 环境下,Block 使用简化了很多,同时 ARC 也更加倾向于把 Block 放到堆上:
__block int val = 10;
__strong blk strongPointerBlock = ^{NSLog(@"val = %d", ++val);};
NSLog(@"strongPointerBlock: %@", strongPointerBlock); // strongPointerBlock: <__NSMallocBlock__: 0x7625120>
__weak blk weakPointerBlock = ^{NSLog(@"val = %d", ++val);};
NSLog(@"weakPointerBlock: %@", weakPointerBlock); // weakPointerBlock: <__NSStackBlock__: 0xbfffdb30>
NSLog(@"mallocBlock: %@", [weakPointerBlock copy]); // mallocBlock: <__NSMallocBlock__: 0x714ce60>
NSLog(@"test %@", ^{NSLog(@"val = %d", ++val);}); // test <__NSStackBlock__: 0xbfffdb18>
只有显式的 __weak 以及纯匿名 Block 是放到栈上的,赋值给 __strong 指针(也就是默认赋值)都会导致在堆上创建 Block。
对于把 Block 作为函数返回值的情况,ARC 也能自动处理。
- (__unsafe_unretained blk) blockTest {
int val = 11;
return ^{NSLog(@"val = %d", val);};
}
NSLog(@"block return from function: %@", [self blockTest]); // block return from function: <__NSMallocBlock__: 0x7685640>