block详解

一 。(一)定义和使用Block
我们使用^运算子来定义一个block变数,而且在block的定义后面加上; 来表示一个完整的述句
 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前面加上__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:                         };
二。 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));// 结果会显示: 1from10is9
float(^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当做参数传入函数,我们通常会使用内嵌的方式来使用Block
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;
     };// 这里是Block的终点
);// 最后的结果为:{“Charles Condomine
", "George", "TomJohn"}
   
   
在上面的例子中,block本身就是函数参数的一部分,在下一个例子中dispatch_apply函数中使用block
void
dispatch_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指定给外面定义的block
 1: // 这是错误的范例,请勿在程式中使用这些语法!!
   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: }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值