IOS --- Blocks详解(一)
一:什么是Blocks
Blocks是C语言的扩充功能,用一句话表示他的扩充功能:带有自动变量(局部变量)的匿名函数。顾名思义,匿名函数就是不带有名称的函数。而C语言是不允许这样的函数存在的,即便是函数指针,也是知道函数名的。
int (*funcptr) (int) = &func;
int result = (*funcptr)(10);
通过Block,源代码中就能使用匿名函数,到这里我们知道了“带有自动变量值的匿名函数”中“匿名函数”的概念。那么带有“自动变量值”究竟是什么呢?
//在C语言中能够使用的变量
* 自动变量(局部变量)
* 函数参数
* 静态变量 (静态局部变量)
* 静态全局变量
* 全局变量
//在函数多次调用之间能够传递值的变量有:
* 静态变量(静态局部变量)
* 静态全局变量
* 全局变量
另外,“带有自动变量值的匿名函数”这一概念并不仅指Blocks,他还应用于许多语言中。在计算机科学中,此概念也称为闭包(Closure),lambda计算等。
二:Blocks语法
首先先来看一个简单的块用法
^void (int event){
printf("buttonID:%d event = %d\n",i,event);
}
如上所示,完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同。
1* 没有函数名
2* 带有^
第一点不同没有函数名,因为它是匿名函数。第二点不同是返回值类型前带有“^”(插入记号,caret)记号。因为OS X,IOS应用程序的源代码中将大量使用Block,所以插入该记号便于查找。
//完整模式
^ 返回值类型 参数列表 表达式
//省略模式
^ 参数列表 表达式
^ 表达式
例如:
^int (int count){return count + 1;}
表达式中的省略问题:
1. 省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有return语句就使用void类型
2.如果不使用参数,参数列表也可以省略:
^void (void){printf("");}
//可以省略参数
^{printf("");}
三:Blocks 类型变量
上面陈述到,在Block语法单从其记述方式上来看,除了没有名称以及带有“^”以外,其他的都和C语言函数定义相同。在定义C语言函数时,就可以将所定义函数的地址赋值给函数指针类型变量中。
int func(int count)
{
return count + 1;
}
int (* funcptr)(int) = &func;
同样地,在Block语法中,可将Block语法赋值给声明为Block类型的变量中,即:源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。Blocks中由Block语法生成的值也被称为“Block”。“Block”即指源代码中的Block语法,也指由block语法所生成的值。
声明Block类型变量的示例如下:
int (^blk)(int);//Block变量的声明
与前面使用函数指针的源代码对比可知,声明Block类型变量仅仅是将声明函数指针类型变量的“*”变为了"^"。该Block变量和一般的C语言变量完全相同。
可作为以下用途使用
* 自动变量
* 函数参数
* 静态变量
* 静态全局变量
* 全局变量
使用Block语法将Block赋值给Block类型变量
int (^blk)(int) = ^(int count){return count+ 1;};
//并且,多个Block类型变量之间可以相互传值
在函数参数和函数返回值中使用Block类型变量
//函数参数
void func(int (^blk)(int)){}
//函数返回值
int (^func()(int))
{
return ^(int count){return count+1;};
}
由此可见,在函数参数和返回值使用Block类型变量时,记述方式极为复杂。这时,我们可以像使用函数指针类型时那样,使用typedef解决该问题。
typedef int(^blk_t)(int);
这样我们将blk_t声明为了类型变量,和原先对比:
//原来
void func( int (^blk)(int) )
void func (blk_t)
{}
//原来
int (^func()(int))
blk_t func(){}
Block类型变量可完全像通常的C语言变量一样使用,因此也可以使用指向Block类型变量的指针,即Block的指针类型变量。
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1;};
blk_t *blkptr = &blk;
(*blkptr)(10);
四:截取自动变量值
”带有自动变量值“究竟是什么?“带有自动变量值”在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;
}
//该源代码中,Block语法的表达式使用的是他之前声明的自动变量fmt 和 val。在Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值,所以在执行Block语法后,即使改写了Block中使用的自动变量值也不会影响Block执行时自动变量的值,
所以执行结果为 val = 10;
五: __block说明符
实际上,自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能改写值。下面我们尝试在BLOCK内改写截获的自动变量值,看看会出现什么结果。
int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf("val = %d \n",val);
//以上为在Block语法外声明的给自动变量赋值的源代码,该源代码会产生编译错误。
若想在Block语法的表达式中将赋值给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符号。
__block int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf("val = %d \n",val);
输出val = 1;
六:截获的自动变量
如果将值赋值给Block中截获的自动变量,就会产生编译错误。
那么截获 Objective-C对象,调用变更该对象的方法也会产生编译错误吗?
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc]init];
[array addObject:obj];
};
//这样是没有问题的,而向截获的变量array赋值则会产生编译错误。
//修改array的值会产生编译错误,但是使用不会产生编译错误。
注意:在使用C语言数组时必须小心使用其指针,源代码如下:
const char text[] = "hello";
void (^blk)(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 (^blk)(void) = ^{printf(text[2]);};