1: int multiplier = 7 ;
2: int (^myBlock)( int ) = ^( int num)
3: {
4: return num * multiplier;
5: };
我们使用下图来解释这个范例(请将文字框的字翻译如下):
我们宣告一个「myBlock」变数,用「^」符号来表示这是一个block。
这是block的完整定义,这个定义将会指定给「myBlock」变数。
表示「myBlock」是一个回传值为整数(int)的block。
它有一个参数,型态也是整数。
这个参数的名字叫做「num」。
这是block的内容。
值得注意的地方是block可以使用和本身定义范围相同的变数,可以想像在上面的例子中 multiplier 和 myBlock 都是某一个函数内定义的两个变数也就是这个变数都在某个函数两个大括号「{」和「 }」中间的区块,因为它们的有效范围是相同的,因此在block中就可以直接使用 multiplier 这个变数,此外当把block定义成一个变数的时,我们可以直接像使用一般函数般的方式使用它:
1: int multiplier = 7 ;
2: int (^myBlock)( int ) = ^( int num)
3: {
4: return num * multiplier;
5: };
6: printf ( "%d" , myBlock( 3 ));
7: //结果会打印出21
X.1.2 直接使用Block
在很多情况下,我们并不需要将block宣告成变数,反之我们可以直接在需要使用block的地方直接用内嵌的方式将block的内容写出来,在下面的例子中qsort_b函数,这是一个类似传统的qsort_t函数,但是直接使用block做为它的参数:
1: char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };
2: qsort_b (myCharacters, 3 ,
3: sizeof ( char *),
4: ^( const void *l, const void *r)//block部分
5: {
6: char *left = *( char **)l;
7: char *right = *( char **)r;
8: return strncmp (left, right, 1 );
9: } //end
10: );
X.1.3 __block 变量
一般来说,在block内只能读取在同一个作用域的变数而且没有办法修改在block外定义的任何变数,此时若我们想要这些变数能够在block中被修改,就必须在前面挂上__block的修饰词,以上面第一个例子中的 multiplier 来说,这个变数在 block 中是唯读的,所以 multiplier = 7 指定完后,在 block 中的 multiplier 就只能是 7 不能修改,若我们在 block 中修改 multiplier ,在编辑时就会产生错误,因此若想要在 block 中修改 multiplier ,就必须在 multiplier 前面加上 __block 的修饰词,请参考下面的范例:
1: __block int multiplier = 7 ;
2: int (^myBlock)( int ) = ^( int num)
3: {
4: if (num > 5 )
5: {
6: multiplier = 7 ;
7: }
8: else
9: {
10: multiplier = 10 ;
11: }
12: return num * multiplier;
13: };
X.2 Block 概要
Block 提供我们一种能够将函数程式码内嵌在一般述句中的方法,在其他语言中也有类似的概念称做「closure」,但是为了配合Objective-C的贯例,我们一律将这种用法称为「block」
X.2.1 Block 的功能
Block 是一种具有匿名功能的内嵌函数,它的特性如下:
如一般的函数般能拥有带有型态的参数。
拥有回传值。
可以撷取被定义的词法作用域(lexical scope)状态。
可以选择性地修改词法作用域的状态。
注:词法作用域(lexical scope)可以想像成是某个函数两个大括号中间的区块,这个区块在程式执行时,系统会将这个区块放入堆叠记忆体中,在这个区块中的宣告的变数就像是我们常听到的区域变数,当我们说block可以撷取同一词法作用域的状态时可以想像block变数和其他区域变数是同一个层级的区域变数(位于同一层的堆叠里),而block的内容可以读取到和他同一层级的其他区域变数。
我们可以拷贝一个block,也可以将它丢到其他的执行绪中使用,基本上虽然block在iOS程式开发中可以使用在C/C++开发的程式片段,也可以在Objective-C中使用,不过在系统的定义上,block永远会被视为是一个Objective-C的物件。
X.2.2 Block 的使用时机
Block 一般是用来表示、简化一小段的程式码,它特别适合用来建立一些同步执行的程式片段、封装一些小型的工作或是用来做为某一个工作完成时的回传呼叫(callback) 。
在新的iOS API中block被大量用来取代传统的delegate和callback,而新的API会大量使用block主要是基于以下两个原因:
可以直接在程式码中撰写等会要接着执行的程式,直接将程式码变成函数的参数传入函数中,这是新API最常使用block的地方。
可以存取区域变数,在传统的callback实作时,若想要存取区域变数得将变数封装成结构才能使用,而block则是可以很方便地直接存取区域变数。
X.3 宣告和建立Block
X.3.1 宣告Block的参考(Reference)
Block 变数储存的是一个block的参考,我们使用类似宣告指标的方式来宣告,不同的是这时block变数指到的地方是一个函数,而指标使用的是「*」,block则是使用「^」来宣告,下面是一些合法的block宣告:
1:
2: void (^blockReturningVoidWithVoidArgument)( void );
3:
4: int (^blockReturningIntWithIntAndCharArguments)( int , char );
5:
6: void (^arrayOfTenBlocksReturningVoidWinIntArgument[ 10 ])( int );
7: X.3.2 建立一个Block
8:
9: 我们使用「^」来开始一个block,并在最后使用「;」来表示结束,下面的范例示范了一个block变数,然后再定义一个block把它指定给block变数:
10:
11: int (^oneFrom)( int );
12:
13: oneFrom = ^(int anInt)
14: {
15: return anInt = - 1 ;
16: };
X.3.3 全域的Block
我在可以在档案中宣告一个全域的block,请参考以下范例:
1: int GlobalInt = 0 ;
2: int (^getGlobalInt)( void ) = ^ ( void ) { return GlobalInt ;};
X.4 Block 和变量
接下来的这一小节我们将会介绍block和变数之间的互动。
X.4.1 变数的型态
我们可以在block中遇到平常在函数中会遇到的变数类型:
l 全域(global)变数或是静态的区域变数(static local)。
l 全域的函数。
l 区域变数和由封闭领域(enclosing scope)传入的参数。
除了上述之外block额外支援了另外两种变数:
在函数内可以使用__block 变数,这些变数在block中是可被修改的。
汇入常数(const imports)。
此外,在方法的实作里,block可以使用Objective-C的实体变数(instance variable)。
下列的规则可以套用到在block中变数的使用:
可以存取全域变数和在同一领域(enclosing lexical scope)中的静态变数。
可以存取传入block的参数(使用方式和传入函数的参数相同)。
在同一领域的区域变数在block中将视为常数(const)。
可以存取在同一领域中以__block 为修饰词的变数。
在block中宣告的区域变数,使用方式和平常函数使用区域变数的方式相同。
下面的例子介绍了区域变数(上述第三点)的使用方式:
1: int x = 123 ;
2: void (^printXAndY)( int ) = ^( int y)
3: {
4: printf ( "%d %d\n" , x, y);
5: };
6: // 将会印出123 456
7: printXAndY( 456 );
8: 就如上面第三点所提到的,在上例中的int x = 123的变量x,在传入block后将视同常数,因此若我们在block中试着去修改x的值时就会产生错误,下面的例子将会无法通过编译:
9:
10: int x = 123 ;
11: void (^printXAndY)( int ) = ^( int y)
12: {
13: // 下面这一行是错的,因为x 在这是一个常数不能被修改。
14: x = x + y;
15: printf ( "%d %d\n" , x, y);
16: };
若在block中想要修改上面的变数x,必须将x宣告加上修饰词__block,请参考接下来这一小节的介绍。
X.4.2 __block 型态变数
我们可以藉由将一个由外部汇入block的变数放上修饰词__block来让这个变数由唯读变成可以读和写,不过有一个限制就是传入的变数在记忆体中必须是一个占有固定长度记忆体的变数,__block修饰词无法使用于像是变动长度的阵列这类不定长度的变数,请参考下面的范例:
1: // 加上__block 修饰词,所以可以在block 中被修改。
2: __block int x = 123 ;
3: void (^printXAndY)( int ) = ^( int y)
4: {
5: x = x + y;
6: printf ( "%d %d\n" , x, y);
7: };
8: // 将会印出579 456
9: printXAndY( 456 );
10: //x 将会变成 579;
11: 下面我们使用一个范例来介绍各类型的变数和block之间的互动:
12:
13: extern NSInteger CounterGlobal;
14: static NSInteger CounterStatic;
15: {
16: NSInteger localCounter = 42 ;
17: __block char localCharacter;
18: void (^aBlock)( void ) = ^( void )
19: {
20: ++ CounterGlobal ; //可以存取。
21: ++ CounterStatic ; //可以存取。
22: CounterGlobal = localCounter; //localCounter在block 建立时就不可变了。
23: localCharacter = ‘a‘ ; //设定外面定义的localCharacter 变数。
24: };
25: ++localCounter; //不会影响的block 中的值。
26: localCharacter = ‘b‘ ;
27: aBlock(); //执行block 的内容。
28: //执行完后,localCharachter 会变成‘a‘
29: }