一个详细的blcok文档,能够系统全面的了解和学习Block
一 、
(一)定义和使用Block首先使用^运算子来定义一个block变数,而且在block的定义后面加上; 来表示一个完整的述句 int multiplier = 7 ;
int (^myBlock)( int ) = ^( int num)
{
return num * multiplier;
};
我们定义一个「myBlock」变数,用「^」符号来表示这是一个block。
这是block的完整定义,这个定义将会指定给「myBlock」变量。
表示「myBlock」是一个回传值为整数(int)的block。
它有一个参数,型态也是整数。
这个参数的名字叫做「num」。
这是block的内容。
打给我们把block定义成一个变数时,我们可以直接像使用一般函数的方式使用它,如下
int multiplier = 7 ;
int (^myBlock)( int ) = ^( int num)
{
return num * multiplier;
};
printf ( "%d" , myBlock(3 ));
//结果会打印出21
(二) 直接使用Block
在很多情况下,我们并不需要将block定义成变量,我们可以直接在需要使用block的地方用内嵌方式将block的内容写出来
二、
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变量
i
nt(^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的时候就会产生错误
下面的无法通过编译
(三)物件和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,constvoid*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 = [NSArrayarrayWithObjects:@"A" ,@"B" , @"C" ,@"A" , @"B" ,@"Z" , @"G" ,@"are" , @" Q" ,nil];// 所有的资料
NSSet*filterSet = [NSSetsetWithObjects:@"A" ,@"B" , @"Z" ,@"Q" , nil];//我们只要这个集合内的资料
BOOL(^test)(id obj,NSInteger idx,BOOL*stop){//只对前5笔做检查
if(idx <5){
if[filterSet containsObject:obj]{
returnYES;
}}
return NO;
};NSIndexSet *indexes = [arrayindexesOfObjectsPassingTest :test];
NSLog(@"indexes:%@",indexes);
(四)该避免使用的方式
在下面的例子中,block是for循环的局部变量,因此应该避免将局部block指定给外面定义的block
// 这是错误的范例,请勿在程式中使用这些语法!!
void dontDoThis() {
void (^blockArray[3])(void);
// 3 个block的阵列
for (int i =0; i < 3; ++i)
{
blockArray[i] = ^{
printf("hello, %d\n", i);
};
// 注意:这个block 定义仅在for回圈有效。
}
}
void dontDoThisEither() {
void (^block)(void);
int i = random():
if (i > 1000) {
block = ^{ printf("got i at: %d\n", i);
};
// 注意:这个block 定义仅在if后的两个大括号中有效。
}
// ...
}
// 结果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)]
// 前5笔资料中,有4笔符合条件,它们的索引值分别是0-1, 3-4
int x = 123 ;
void (^printXAndY)(int ) = ^( int y)
{
// 下面这一行是错的,因为x在这是一个常数不能被修改。
x = x + y;
printf ( "%d %d\n" , x, y);
};
若想修改上面的变量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’;
}
char *myCharacters[3 ] = { "TomJohn" ,"George" , "Charles Condomine" };
qsort_b (myCharacters,3 ,sizeof (char *),^( constvoid *l, constvoid *r)//block部分
{
char *left = *(char **)l;
char *right = *(char **)r;
return strncmp (left, right, 1 );
}
//end
);
(三)__block 变量
一般来说,在block内只能读取同一个作用域的变量并且没有办法修改block外定义的任何变量。此时如果我们想让这些变量在block中被修改,就必须在前面加上__block修饰词
以第一个例子为例multiplier来说,这个变量在block中是只读的,multiplier只能是7不能修改,若我们想在block中修改multiplier,在编译的时候会报错,必须在multiplier前面加上__block
__block int multiplier =7 ;
int (^myBlock)( int ) = ^( int num)
{
if (num > 5 ){
multiplier = 7 ;
}
else
{
multiplier = 10 ;
}
return num * multiplier;
};