什么是Block
Block是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。
C语言中可能使用的变量:
- 自动变量(局部变量)
- 函数的参数
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
“带有自动变量值的匿名函数”这一高年不仅指Blocks,它还存在于其他许多程序语言中。在计算机科学中,此概念也被称为闭包(Closure)、lambda计算等。
Block模式
完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同:
(1)没有函数名。
(2)带有“^”。
第一点,因为它是匿名函数。
第二点,返回值类型前带有“^”(插入记号,caret)记号。macOs、iOS应用程序的源代码中将大量使用Block,所以插入该记号便于查找。
^ 返回值类型 参数列表 表达式
返回值类型、参数列表都是可以省略的。
Block类型变量
在Block语法下,可将Block语法赋值给声明为Block类型的变量中。即源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。Blocks中由Block语法生成的值也被称为“Block”。
声明Block类型变量的示例如下:
int (^block)(int);
声明Block类型变量仅仅是将声明函数指针类型变量的“*”变为“^”。该Block类型变量与一般的C语言变量完全相同,可作为一下用途使用。
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
使用Block语法将Block赋值为Block类型变量:
int (^block)(int) = ^(int count) {return count + 1;};
在函数参数中使用Block类型变量可以向函数传递Block:
void func(int (^block) (int)) {
}
在函数返回值中指定Block类型,可以将Block作为函数的返回值返回。
int (^func()) (int) {
return ^(int count) {return count+1;};
}
由此可知,函数参数和返回值中使用Block类型变量时,记述方式极为复杂。这时,我们可以像使用函数指针类型时那样,使用typedef类解决该问题。
typedef int (^block_t) (int);
/** 原来的记述方式
void func (int (^block) (int))
*/
void func (block_t block)
/** 原来的记述方式
void func (int (^block) (int))
*/
block_t func()
截获自动变量的值
通过Block语法和Block类型变量的说明,我们已经理解了“带有自动变量值的匿名函数”中的“匿名函数”。而“带有自动变量值”是什么呢?“带有自动变量值”在Blocks中表现为“截获自动变量值”。截获自动变量值的实例如下:
int main() {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^block) (void) = ^{
printf(fmt, val);
};
val = 2;
fmt = "These values were changed. val = %d\n";
block();
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中,因此这些值被保存,从而在执行块时使用。
这就是自动变量值的截获。
__block修饰符
自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能修改该值。如果尝试改写截获的自动变量值:
int val = 0;
void (^block) (void) = ^{
val = 1;
};
block();
printf("val = %d\n", val);
以上为在Block语法外声明的给自动变量赋值的源码。该源码会产生编码错误。
error: variable is not assignable (missing __block type specifier)
void (^block) (void) = ^{
val = 1;
};
若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block修饰符。该源码中,如果给自动变量声明int val附加__block修饰符,就能实现在Block内部赋值。
__block int val = 0;
void (^block) (void) = ^{
val = 1;
};
block();
printf("val = %d\n", val);
该源码的执行结果为:
val = 1
使用附有__block修饰符的自动变量可在Block赋值,该变量成为__block变量。
截获的自动变量
如果将值赋值给Block中截获的自动变量,就会产生编译错误。
int val = 0;
void (^block) (void) = ^{
val = 1;
};
编译该源码错误为:
error: variable is not assignable (missing __block type specifier)
void (^block) (void) = ^{
val = 1;
};
那么截获OC对象,调用该对象的方法也会产生编译错误吗?
id array = [[NSMutableArray alloc] init];
void (^block) (void) = ^ {
id obj = [[NSObject alloc] init];
[array addObject:obj];
};
这是没有问题的,而向截获的变量array赋值则会产生编译错误。该源码中截获的变量值为NSMutableArray类的对象。如果用C语言来描述,即是截获NSMutableArray类对象用的结构体指针。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值却不会有任务问题。
id array = [[NSMutableArray alloc] init];
void (^block) (void) = ^ {
array = [[NSMutableArray alloc] init];
};
error: variable is not assignable (missing __block type specifier)
array = [[NSMutableArray alloc] init];
~~~~~ ^
这种情况下,需要给截获的自动变量附加__block修饰符。
__block id array = [[NSMutableArray alloc] init];
void (^block) (void) = ^ {
array = [[NSMutableArray alloc] init];
};
另外,在使用C语言数组时必须小心使用其指针。
const char text[] = "hello";
void (^block) (void) = ^ {
printf("%c\n", text[2]);
};
只是使用C语言的字符串字面量数组,而并没有向截获的自动变量赋值,因此看似没有问题。但实际上会产生以下编译错误:
error: cannot refer to declaration with an array type inside block
printf("%c\n", text[2]);
note: declared here
const char text[] = "hello";
^
这是因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获。这时,使用指针可以解决该问题。
const char *text = "hello";
void (^block) (void) = ^ {
printf("%c\n", text[2]);
};