1 截获局部变量值
通过Block语法和Block类型变量的说明,我们已经理解了“带有局部变量值的匿名函数”中的“匿名函数”。而“带有局部变量值”究竟是什么呢?“带有局部变量值”在Blocks中表现为“截获局部变量值”。截获局部变量值的实例如下:
int main(int argc, const char * argv[])
{
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt, val);};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
return 0;
}
该源代码中,Block语法的表达式使用的是它之前声明的局部变量fmt和val。Blocks中,Block表达式截获所使用的局部变量的值,即保存该局部变量的瞬间值。因为Block表达式保存了局部变量的值,所以在执行Block语法后,即使改写Block中使用的局部变量的值也不会影响Block执行时局部变量的值。该源代码就在Block语法后改写了Block中的局部变量val和fmt下面我们一起看一下执行结果:
val = 10
执行结果并不是改写后的值“These values were changed. val = 2”,而是执行Block语法时的局部变量的瞬间值。该Block语法在执行时,字符串指针“val = %d\n”被赋值到局部变量fmt中,int值10被赋值到局部变量val中,因此这些值被保存(即被截获),从而在执行块时使用。
这就是局部变量值的截获。
2 __block说明符
实际上,局部变量值截获只能保存执行Block语法瞬间的值。保存后就不能改写该值。下面我们来深度改写截获的局部变量的值,看看会出现什么结果。下面的源代码中,Block语法之前声明的局部变量val的值被赋予1。
int val = 10;
void (^blk)(void) = ^{val = 1;};
blk();
printf(@"val = %d\n", val);
以上为在Block语法外声明的给局部变量赋值的源代码。该源代码会产生编译错误:
若想在Block语法的表达式中将值赋给在Block语法外声明的局部变量,需要在该局部变量上附加__block说明符。该源代码中,如果给局部变量声明int val附加__block说明符,就能实现在Block内赋值。
__block int val = 10;
void (^blk)(void) = ^{val = 1;};
blk();
printf(@"val = %d\n", val);
该源代码的执行结果为:
val = 1
使用附有__block说明符的局部变量可以Block中赋值,该变量称为__block变量。
3 截获的局部变量
如上面所看到的,如果将值赋值给Block中截获的局部变量,就会产生编译错误。那么截获Objective-C对象,调用变更该对象的方法也会产生编译错误吗?
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc] init];
[array addObject:obj];
};
这是没有问题的,而向截获的变量array赋值则会产生编译错误。该源代码中截获的变量值为NSMutableArray类的对象。如果用C语言来描述,即是截获NSMutableArray类对象用的结构体实例指针。虽然赋值给截获的局部变量array的操作会产生编译错误,但使用截获的值却不会有任何问题。
另外,在使用C语言数组时必须小心使用其指针。源代码示例如下:
const char text[] = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};
只是使用C语言的字符串字面量数组,页并没有向截获的局部变量赋值,因此看似没有问题。但实际上会产生以下编译错误:
这是因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获。这时,使用指针可以解决该问题:
const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);
};