3.5 块编程
C语言的运行时特性中包括了块,标准C工作组的 N1370: Apple’s Extensions to C 中(其中也包括垃圾回收)对块进行了定义。作为C语言的扩展,Objective-C在OSX 10.6及iOS 4.0以后支持块语法。块运行时也会被集成到LLVM的compiler-rt子项目存储库中。
3.5.1 块的特点
一些面向对象的动态语言如ruby、groovy,都提供了对块的支持(在groovy中,块被称作为闭包“closure”)。块是用一对{}括号括起来的多个语句的集合。类似于函数,但不同于函数,可以把块作为表达式或变量的一部分,或者作为参数传递。在作为参数传递块时,代码被作为数据的一部分进行传递。
块具有以下特征:
同函数一样,有类型化参数列表。
有返回结果或者要申明返回类型。
能获取同一作用域(与块所在同一作用域)内的状态。
可以修改同一作用域的状态(变量)。
与同一范围内的其他块同享变量。
在作用域释放后能继续共享和改变同一范围内的变量。
除以上特点外,甚至可以复制块并传递到其他后续执行的线程,编译器和运行时负责把所有块引用的变量保护在所有块的拷贝的生命周期内。当然,这已经超出了本章的范围,可以参考苹果官方文档来了解这些内容。
3.5.2 Objective-C 中的块
对于C和C++,块是变量,但对于Objective-C,块仍然是对象。下面简单介绍Objective-C中的块。
1.块变量声明
用^操作符声明一个块变量的开始,分号表示块结束,如下代码所示:
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num multiplier;
};
块语法比较奇怪,块变量声明的解释如图3-2所示。
块变量的声明语句从前至后分为了几部分:
返回值类型,如int、double,如果未显式地声明块的返回值类型,可能会自动从块代码中推断返回类型(通过return语句)。
块变量名用括号括住,块变量名前加^符号。
参数类型用括号括住,多个参数以逗号分隔,如果参数列表为void,而且返回类型依靠推断,可以省略参数列表的void。
等号,将后面的块赋值给前面的块变量(即myBlock)。
以^开头并以;结束的块定义。
块定义中又分为以下两个部分(除去开头的^和结尾的;外):
参数列表,同函数的参数列表。
块体,同函数体。
值得注意的是,块可以使用同一作用域内定义的变量,而函数不行。
一旦声明了块,你可以像使用函数一样调用它:
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num multiplier;
};
printf ( "%d", myBlock(3));
2.行内块
有时候,你不准备重复使用某个块,因此你不必为它想一个名称。那你可以使用行内块而不用声明为块变量。以下代码来自苹果文档:
// gsort_b类似标准的 gsort_r 函数,但它最后一个参数是一个块。
char myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
qsort_b(myCharacters, 3, sizeof(char ), ^(const void l, const void r) {
char left = (char )l;
char right = (char )r;
return strncmp(left, right, 1);
});
// myCharacters 现在是 { "Charles Condomine", "George", TomJohn" }
在qsort_b 方法调用中,第4个参数就是一个匿名的块(行内块)。匿名块跟块变量不同,它没有变量名,因此你无法重用匿名块。下次调用这个块时,必须把整个块定义的代码再复制一遍。
3.__block关键字
块允许访问本地变量。这很重要。它使得我们在线程间共享变量变得简单,而且,你可以规定一个本地变量是否可以写,这可通过使用__block关键字,这是一种类类似register、auto和static存储类型修饰符。
用__block修饰的变量,可以在所有同一作用域内的块,以及块复制之间共享数据。在指定作用域内的多个块能同时使用共享变量。
如同块,__block变量也使用栈存储。如果使用block_copy拷贝块(或者向块发送copy消息),变量被拷贝到堆里。而且,__block变量的地址随后就会改变。
__block变量有两个限制:不能是可变长度的数组,也不能是包含C99可变长度数组的结构体。
下面显示了__block变量的使用:
__block int x = 123; // x 是块可写的
void (^printXAndY)(int) = ^(int y) {
x = x + y;
printf("%d %d\n", x, y);
};
printXAndY(456); // 打印出: 579 456
// x 现在的值是:579
下面显示了在块中使用多种类型的变量:
extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
NSInteger localCounter = 42;
__block char localCharacter;
void (^aBlock)(void) = ^(void) {
++CounterGlobal;
++CounterStatic;
CounterGlobal = localCounter;
localCharacter = 'a';
};
++localCounter;
localCharacter = 'b';
aBlock();
}