2.1 Blocks概要
2.1.1 什么是Blocks
Blocks是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。
顾名思义,所谓匿名函数就是不带有名称的函数。C语言的标准标准不允许存在这样的函数。例如以下源代码:
int func(int count)
它声明了名称为func的函数。下面的源代码中为了调用该函数,必须使用该函数的名称func.
int result = func(10)
如果像下面这样,使用函数指针来代替直接调用函数,那么似乎不用知道函数名也能够使用该函数。
int result = (*funcptr)(10)
但其实使用函数指针也仍然需要知道函数名称。像以下源代码这样,在赋值给函数指针时,若不使用想赋值的函数的名称,就无法取得该函数的地址。
int(funcptr)(int) = &func;
int result = (funcptr)(10);
而通过Blocks,源代码中就能使用匿名函数,即不带名称的函数。对于程序员而言,命名就是工作的本质,函数名、变量名、方法名、属性名、类名、和框架名等都必须具备。而能够编写不带名称的函数对程序员来说相当具有吸引力。
到这里,我们知道了“带有自动变量值的匿名函数”中“匿名函数”的概念。那么“带有自动变量值”究竟是什么呢?
首先回顾一下在C语言的函数中可能使用的变量。
* 自动变量(局部变量)
* 函数的参数
* 静态变量(静态局部变量)
* 静态全局变量
* 全局变量
其中,在函数的多次调用之间能够传递值的变量有:
* 静态变量(静态局部变量)
* 静态全局变量
* 全局变量
虽然这些变量的作用域不同,但是在整个程序当中,一个变量总保持在一个内存区域,因此,虽然多次调用函数,但该变量值总能保持不变,在任何时候以任何状态调用,使用的都是同样的变量值。
>```
int buttonId = 0
void buttonCallback(int event)
{
printf("buttonId:%d event=%d\n",buttonId,event);
}
如果只有一个按钮,那么该源代码毫无问题,可正常运行。但有多个按钮时会如何呢?
int buttonId;
void buttonCallback(int event)
{
printf("buttonId:%d event=%d\n",buttonId,event);
}
void setButtonCallbacks()
{
for(int i=0;i<BUTTON_MAX,++i){
buttonId = i;
setButtonCallback(BUTTON_IDOFFEST+i,&buttonCallback);
}
}
该源代码的问题很明显。全局变量buttonId只有一个,所有回调都使用for循环最后的值。当然如果不使用全局变量,回调方会将按钮的ID作为函数参数传递,就能解决问题。
>```
>void buttonCallback(int buttonId,int event)
>{
> printf(buttonId:%d event=%d\n",buttonId,event);
>}
>```
但是,回调方在保持回调函数的指针以外,还必须保持回调方的按钮ID。 C++和Objective-C使用类可保持变量值且能够多次持有该变量自身。它会声明持有成员变量的类,由类生成的实例或对象保持该成员变量的值。我们来思考一下刚才例子中用来回调按钮的类。
>```
>@interface ButtonCallbackObject:NSObject
>{
> int _buttonId;
>}
>@end
>
>@implementation ButtonCallbackObject
>-(id)initWithButtonId:(int)buttonId
>{
> self = [super init];
> _buttonId = buttonId;
> return self;
>}
>
>-(void)callback:(int)event
>{
> NSLog(@"buttonId%d event=%d\n",_buttonId,event);
>}
>@end
>```
如果使用该类,由于对象保持按钮ID,因此回调方只需要保持对象即可。可如下使用:
>
void setButtonCallbacks
{
for(int i=0;i<BUTTON_MAX;++i)
{
ButtonCallbackObject *callbackObj = [[ButtonCallBackObject alloc]initWithButtonId:i];
setButtonCallbackUsingObject(BUTTON_IDOFFSET,callbackObj);
}
}
但是,由此源代码可知,声明并实现C++、Objective-C的类增加了代码的长度。
这时我们就要用到Blocks了。Blocks提供了类似由C++和Objective-C类生成实例或对象来保持变量值的方法,其代码量与编程C语言函数差不多。如“带有自动变量值”,Blocks保持自动变量的指。下面我们使用Blocks实现上面的按钮回调。
>```
>void setButtonCallbacks
{
for(int i=0;i<BUTTON_MAX;++i)
{
setButtonCallbackUsingBlock(BUTTON_IDOFFEST+i,^(int event)){
printf("buttonId:%d event=%d\n",i,event);
};
}
}
>```
Blocks的语法和保持栋变量值等将在后面详细说明,该源代码将“带有自动变量i值的匿名函数”设定为按钮的回调。Blocks中将该匿名函数部分称为“Block literal”,或者简称为“Block”.
像这样,使用Blocks可以不声明C++和Objective-C类,也没有使用静态变量、静态全局变量或全局变量时的问题。仅用编写C语言函数的源代码量即可使用带有自动变量会的匿名函数。
另外,“带有自动变量会的匿名函数”这一概念并不仅指Blocks,它还存在于其他许多程序语言中。在计算机科学中,此概念也称为闭包(Closure)、lambda计算。Objective-C的Block在其他程序语言中的名称如表2-1所示。
#####表 2-1 其他程序语言中Block的名称
|程序语言|Block的名称|
---|---
C+ Blocks|Block|
Smalltalk|Block|
Ruby|Block|
LISP|Lambda|
Python|Lambda|
C++11|Lambda|
Javascript|Anonymous function|
##2.2 Blocks模式
###2.2.1 Block语法
Block语法如下:
>```
^(int event)
{
printf("buttonId:%d event=%d\n",i,event);
}
实际上,该Block语言使用了省略方式,其完整像是如下:
^void(int event){ printf("buttonId:%d event=%d\n",i,event); }
如上所示,完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同。
- 没有函数名
- 带有“^”
第一点不同是没有函数名,因为它是匿名函数。第二点不同是返回值类型前带有“^”(插入几号,caret)记号。因为OSX、iOS应用程序的源码中将大量使用Block,所以插入该记号便于查找。
以下为Block语法的BN范式。
^返回值类型 参数列表 表达式
省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有return语句就使用void类型。表达式中含有含有多个return语句时,所有return的返回值必须相同。
2.2.2 Block类型变量
声明Block类型变量的示例如下:
int (^blk)(int)
与前面的使用函数指针的源代码对比可知,声明Block类型变量仅仅是将声明函数指针类型的变量“*”变回“^”。该Block类型变量与一般的C语言变量完全相同,可作为以下用途使用。
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
那么,下面我们试着使用Block语法将Block赋值为Block类型变量。
int (^blk)(int) = ^(int count){return count+1;}
2.2.3 截获自动变量值
Block 带有自动变量值的匿名函数。“带有自动变量值”在Blocks中变现为“截获自动变量值”。截获自动变量值的实例如下:
int main()
{
int dmy = 256;
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;
}
Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行时自动变量的值。
2.2.4 __block说明符
实际上,自动变量值截获只能保存Block语法瞬间的值。保存后就不能改写该值。