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定义成一个变数时,我们可以直接像使用一般函数的方式使用它,如下
1: int multiplier = 7 ;2: int (^myBlock)( int ) = ^( int num)3: {
4: return num * multiplier;5: };
6: printf ( "%d" , myBlock( 3 ));7: //结果会打印出21
(二) 直接使用Block在很多情况下,我们并不需要将block定义成变量,我们可以直接在需要使用block的地方用内嵌方式将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: );
(三)__block 变量一般来说,在block内只能读取同一个作用域的变量并且没有办法修改block外定义的任何变量。此时如果我们想让这些变量在block中被修改,就必须在前面加上__block修饰词以第一个例子为例multiplier来说,这个变量在block中是只读的,multiplier只能是7不能修改,若我们想在block中修改multiplier,在编译的时候会报错,必须在multiplier前面加上__block1: __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: };
二。 Block 提供我们一种能够将函数程式码内嵌在一般述句中的方法,在其他语言中也有类似的概念称做「closure」,但是为了配合Objective-C的贯例,我们一律将这种用法称为「block」
(一 )Block的功能
Block是一种具有匿名功能的内嵌函数,它的特性如下:
如一般的函数般能拥有带有型态的参数
拥有回传值
可以撷取被定义的词法作用域状态
可以选择性的修改词法作用域的状态
注 :词法作用域可以想象成某个函数两个大括号中间的区块,这个区块在程序执行时,系统会将这个区块放入堆叠记忆体中,在这个区块里定义的变量就是我们常说的局部变量,当我们说block可以撷取同一词法作用域状态时可以想象成block和其他局部变量在同一区块,block的内容可以读取到和它在同一作用域的其他变量。
我们可以拷贝一个block,也可以将它丢到其他执行绪中使用 。基本上虽然block在iOS程式开发中可以使用在C/C++开发的程式片段,也可以在Objective-C中使用,不过在系统的定义上,block永远会被视为是一个Objective-C的物件。
(二)Block的使用时机Block 一般是用来表示、简化一小段的程式码,它特别适合用来建立一些同步执行的程式片段、封装一些小型的工作或是用来做为某一个工作完成时的回传呼叫(callback) 。
在新的iOS API中block被大量用来取代传统的delegate和callback,而新的API会大量使用block主要是基于以下两个原因:
可以直接在程式码中撰写等会要接着执行的程式,直接将程式码变成函数的参数传入函数中,这是新API最常使用block的地方。
可以存取区域变数,在传统的callback实作时,若想要存取区域变数得将变数封装成结构才能使用,而block则是可以很方便地直接存取区域变数。
三 。定义和创建Block
(一)定义Block的参数
Block变量存储的是一个block的参数,我们使用类似定义指针的方式来宣告,不同的是这时block变量指到的地方是一个函数,而不是指针使用的是* block则是使用^来宣告,下面是一些合法的block宣告:
void(^blockReturningWithVoidArgument)(void); // 回传void,参数也是void的block
int(^blockReturningIntWithIntAndCharArguments)(int,char) // 回传整型,两个参数分别是整型和字符型
void(^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int); // 回传void ,含有10个block的数组,每个block都有一个整型的参数
我们使用^来开始一个block 并在最后用;表示结束下面示范一个block变量,然后定义一个block把它指定给block变量
int(^oneFrom)(int);// 定义block变量
oneFrom = ^(int anInt) // 定义block的内容并指给上面定义的变量
{
return anInt = -1;
};
四 。Block和变量
接下来的将介绍block和变量之间的互动
(一) 变量的型态
我们可以在block中遇到平常在函数中会遇到的变量类型
全局变量(global)或是静态的局部变量(static local)
全局的函数
局部变量和由封闭领域(enclosing scope)传入的参数
除了上述之外block额外支援另外两种变量:
在函数内可以使用__block变量,这些变量在block中可被修改
汇入常数(const imports)
此外,在方法的实际操作里。block可以使用Object—C的实体变数(instance variable)。
下列规则可以套用在block中变量的使用
可以存取全局变量和在同一领域(enclosing lexical scope)中的静态变量
可以存取传入block的参数(使用方式和传入函数的参数相同)
在同一领域的局部变量在block中视为常量(const)
可以存取在同一领域中以__block为修饰词的变量
在block中定义的局部变量使用方式和平常函数使用局部变量的方式相同
下面的例子介绍局部变量的使用方式
int x = 123;
void(^printAAndY)(int) = ^(int y)
{
printf(@“%d%d”,x,y);
};
// 将会打印出123 456
printXAndY(456);
就如上面所提到,变量x,在传入block后视为常量,因此我们在block中试着去修改x的时候就会产生错误
下面的无法通过编译
int x = 123 ;
11: void (^printXAndY)( int ) = ^( int y)12: {
13: // 下面这一行是错的,因为x 在这是一个常数不能被修改。14: x = x + y;
15: printf ( "%d %d\n" , x, y);16: };
若想修改上面的变量x,必须将x加上修饰词__block,参考下一小节
(二) __block型态变量
__block 修饰的变量由只读变成可读可写的,不过有一个限制就是传入的变量在堆中必须占有固定内存的 。无法修饰像是变动长度的阵列这类的变量
// 加上__block修饰词,在__block中可被修改
__block int x = 123;
void(^printXAndY)(int) = ^(int y)
{
x = x + y;
printf(@“%d%d”,x,y);
};
// 将会打印出 579 345
printXAndY(456);
// x将会变成579;
// 下面使用一个范例来介绍各类型的变量和block之间的互动
extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
NSInteger localCounter = 42;
__block char localCharacter;
void (^aBlock)(void) = ^(void)
{
++CounterGlobal;// 可以存取
++CounterStatic;// 可以存取
CounterGlobal = localCounter;// localCounter在block建立时就不可改变
localCharacter = ‘a’; // 设置外面定义的localCharacter变量
};
++localCounter;// 不会影响到block中的值
localCharacter = ‘b’;
aBlock();// 执行block的内容
// 执行完后,localCharacter会变成’a’;
}
(三)物件和Block变量在拥有参考计数(reference-counted)的环境中,若我们在block中参考到Objective-C的物件,在一般情况下它会自动增加物件的参考计数,不过,若以__block为修饰的物件,参考计数则不受影响在OC中使用block,以下几个和记忆体管理的事是需要额外注意的若直接存取实体变量(instance variable)。self的参考计数将加1若透过变量存取实体变量的值,则只变量的参考计数将加1以下代码说明上述两个问题,假设instanceVariale是实体变量dispatch_async(queue,^{doSomethingWithObject(instanceVariable); // 因为直接存取实体变量,所以self的retain count会加1});id localVariable = instanceVariable;dispatch_async(queue,^{doSomethingWithObject(localVariable);// localVariable是存取值,所以这时只有localVariable的retain count加1 // self 的retain Count并不会增加});五。 使用Block(一)呼叫一个Block当block定义成一个变量时,我们可以像使用一般函数的方式来使用它,参考下面两个范例int(^oneFrom)(int) = ^(int anInt){return anInt-1;};printf(@“1from10is%d”,oneFram(10));// 结果会显示: 1from10is9float(^distanceTraveled)(float,float,float)= ^(float startingSpeed,float acceleration,float time){float distance = (startingSpeed *time)-(0.5*acceleration*time*time);return distance;};float howFar = distanceTraveled(0.0,9.8,1.0); // howFar会变成4.9(二) 将Block当做函数的参数在一般情况下,若是Block当做参数传入函数,我们通常会使用内嵌的方式来使用Blockchar*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;};// 这里是Block的终点);// 最后的结果为:{“Charles Condomine
", "George", "TomJohn"}在上面的例子中,block本身就是函数参数的一部分,在下一个例子中dispatch_apply函数中使用blockvoiddispatch_apply(size_t iterations,dispatch_t queue ,void(^block)(size_t));这个函数将block提交到发送队列(dispatch queue)中来执行多重的呼叫,只有当队列中的工作都执行完成才会回传,这个函数拥有三个变量,最后一个参数就是block,参考下面的范例size_t = 10;dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);dispatch_apply(count,queue,^(size_t){printf(@“%u\n”,i);});(三)将BLock当做方法的参数我们可以像传递一般参数的方式来传递block,下面示范在一个队列的前5笔资料中取出我们想要的资料的索引值NSArray *array = [NSArray arrayWithObjects:@"A" , @"B" , @"C" , @"A" , @"B" , @"Z" , @"G" , @"are" , @" Q" ,nil]; // 所有的资料NSSet*filterSet = [NSSet setWithObjects:@"A" , @"B" , @"Z" , @"Q" , nil];// 我们只要这个集合内的资料BOOL(^test)(id obj,NSInteger idx,BOOL*stop){// 只对前5笔做检查if(idx <5){if[filterSet containsObject:obj]{return YES;}}return NO;};NSIndexSet *indexes = [array indexesOfObjectsPassingTest :test];NSLog(@“indexes:%d”,indexes);// 结果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)]// 前5笔资料中,有4笔符合条件,它们的索引值分别是0-1, 3-4(四)该避免使用的方式在下面的例子中,block是for循环的局部变量,因此应该避免将局部block指定给外面定义的block1: // 这是错误的范例,请勿在程式中使用这些语法!!
2: void dontDoThis() {
3: void (^blockArray[3])(void); // 3 个block 的阵列
4: for (int i = 0; i < 3; ++i) {
5: blockArray[i] = ^{ printf("hello, %d\n", i); };
6: // 注意: 这个block 定义仅在for 回圈有效。
7: }
8: }
9: void dontDoThisEither() {
10: void (^block)(void);
11: int i = random():
12: if (i > 1000) {
13: block = ^{ printf("got i at: %d\n", i); };
14: // 注意: 这个block 定义仅在if 后的两个大括号中有效。
15: }
16: // ...
17: }