您的位置:首页 > 移动开发 > IOS开发

IOS开发之Block 编程(翻译官方文档)

2014-01-08 13:09 387 查看
本文转自:http://blog.csdn.net/perfect_promise/article/details/7757746

注:小弟才疏学浅,英文水平够烂,若有不正确或误导的地方,请大家指出,欢迎大家指正和修改。本文中涉及的词法范围:作用范围,例如if{}else{},两个{}分别是if和else的作用范围。

介绍

Block对象是一个C级别的语法和运行机制。它与标准的C函数类似,不同之处在于,它除了有可执行代码以外,它还包含了与堆、栈内存绑定的变量。因此,Block对象包含着一组状态数据,这些数据在程序执行时用于对行为产生影响。

你可以用Block来写一些可以传到API中的函数语句,可选择性地存储,并可以使用多线程。作为一个回调,Block特别的有用,因为block既包含了回调期间的代码,又包含了执行期间需要的数据。

作为Mac OS X v10.6Xcode开发工具附带的工具,Block在GCC和Clang中同样可用。你能在Mac OS X v10.6及其以上版本和iOS 4.0及其以上版本中使用Block。.Block的运行是开源的,因此你能在LLVM’s
compiler-rtsubprojectrepository里面找到它。Block也已经被提交到C标准工作组作为 N1370:
Apple’sExtensionstoC。由于Objective-C和C++都是衍生自C,block被设计为可同时兼容这三种语言。

你应该阅读这篇文档,去学习Block是什么,以及在C、C++和OC中如何使用Block使你的程序更加的高效和更易于维护。

声明和使用Block

用^操作符来声明一个Block变量,并指明Block述句的开始。Block的主体部分包含在 
{}内,像下面的例子中一样(与C语法一样,“;”指明语句的结束):
 

intmultiplier=7;

int(^myBlock)(int)=^(intnum){

returnnum*multiplier;

};



注意:Block可以使用定义范围之内的任何变量。

如果你把Block声明为一个变量,你以后就可以像调用一个方法一样使用它:

intmultiplier=7;

int(^myBlock)(int)=^(intnum){

returnnum*multiplier;

};


printf("%d",myBlock(3));

//prints"21"

直接使用Block

很多情况下,你不需要声明Block变量;你只是简单地写一个Block语句内联在需要使用它作为参数的地方。下面的例子使用了 
qsort_b方法, qsort_b方法与标准的qsort_r类似,只是用Block作为它的最后一个参数。


char*myCharacters[3]={"TomJohn","George","CharlesCondomine"};


qsort_b(myCharacters,3,sizeof(char*),^(constvoid*l,constvoid*r){

char*left=*(char**)l;

char*right=*(char**)r;

returnstrncmp(left,right,1);

});


//myCharactersisnow{"CharlesCondomine","George","TomJohn"}

CocoaBlock

Cocoa框架中有几个方法使用Block作为参数,通常是在执行对象的操作集合,或者操作完成后使用它作为回调。下面的例子向我们展示了

在NSArray
 对象的方法
sortedArrayUsingComparator:怎样使用Block
.。这个方法只有单一的参数,block被定义为
NSComparator
 局部变量:

NSArray*stringsArray=[NSArrayarrayWithObjects:

@"string1",

@"String21",

@"string12",

@"String11",

@"String02",nil];


staticNSStringCompareOptionscomparisonOptions=NSCaseInsensitiveSearch|NSNumericSearch|

NSWidthInsensitiveSearch|NSForcedOrderingSearch;

NSLocale*currentLocale=[NSLocalecurrentLocale];


NSComparatorfinderSortBlock=^(idstring1,idstring2){


NSRangestring1Range=NSMakeRange(0,[string1length]);

return[string1compare:string2options:comparisonOptionsrange:string1Rangelocale:currentLocale];

};


NSArray*finderSortArray=[stringsArraysortedArrayUsingComparator:finderSortBlock];

NSLog(@"finderSortArray:%@",finderSortArray);


/*

Output:

finderSortArray:(

"string1",

"String02",

"String11",

"string12",

"String21"

)

*/


_block变量

Block的一个强大的特性是,它能在相同的词法范围内修改变量值。Block能修改变量是通过_block存储类型标示符。与 “Cocoa
Block” 中的例子一样,你能使用一个block变量来计算有多少字符串与下面例子中是相同的。Block直接被用,并且用 
currentLocale作为一个只读变量在block中。


NSArray*stringsArray=[NSArrayarrayWithObjects:

@"string1",

@"String21",//<-

@"string12",

@"String11",

@"Strîng21",//<-

@"Striñg21",//<-

@"String02",nil];


NSLocale*currentLocale=[NSLocalecurrentLocale];

__blockNSUIntegerorderedSameCount=0;


NSArray*diacriticInsensitiveSortArray=[stringsArraysortedArrayUsingComparator:^(idstring1,idstring2){


NSRangestring1Range=NSMakeRange(0,[string1length]);

NSComparisonResultcomparisonResult=[string1compare:string2options:NSDiacriticInsensitiveSearchrange:string1Rangelocale:currentLocale];


if(comparisonResult==NSOrderedSame){

orderedSameCount++;

}

returncomparisonResult;

}];


NSLog(@"diacriticInsensitiveSortArray:%@",diacriticInsensitiveSortArray);

NSLog(@"orderedSameCount:%d",orderedSameCount);


/*

Output:


diacriticInsensitiveSortArray:(

"String02",

"string1",

"String11",

"string12",

"String21",

"Str\U00eeng21",

"Stri\U00f1g21"

)

orderedSameCount:2

*/

更详细的内容请查看 “Blocks
和变量.”

Block功能

Block是一个匿名的内嵌代码集:

与方法一样,有一个类型参数列表

有一个隐形或声明的返回类型

能从它定义的词法范围内获取状态

能有选择性地修改词法范围中的状态

能共享相同词法范围内其他块定义的修改的潜在性

词法范围被销毁后仍能继续在已定义的词法范围内共享和修改状态

你能复制一个block,并把它传递给其他线程来延迟执行(或者,在它自己的线程内,做一个运行环)。编译和运行过程中,从block中引用的所有变量都保留乐一份block的副本。block不仅适用于纯C和C++,同时block也是一个Objective-C对象。

用法

Blocks通常表示比较小的,独立的代码段。因此,它特别适用于可能被同时执行的封装单元工作的模式,或者是集合中的项目,或者是当另一个操作完成后的一个回调。

Blocks之所以能替代传统的回调方法主要有以下两个理由:

它允许你在调用点写代码,调用点稍后会在方法实现段被执行。

Blocks通常也是框架方法中的参数。

它允许访问局部变量。

与其使用回调,需要一个包含所有上下文信息的数据结构,你只需要执行一个操作,直接访问局部变量即可。

声明block参考

Block变量持有Block引用。声明它的语法与在函数中声明指针类似,用 
^代替
 
*。
 其余部分,与C类型系统,具有完全的互操作性。以下时所有有效块的变量声明:

void(^blockReturningVoidWithVoidArgument)(void);

int(^blockReturningIntWithIntAndCharArguments)(int,char);

void(^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);

Block支持可变参数(
...
)参数。不带任何参数的block,在参数列表中必须指定为void。Block的设计考虑到类型安全,通过提供给编译器全套的元数据来验证block的使用、传递参数到block中和返回值分配。一个块引用可以转换到任意类型的指针,反之亦然。但是,你不能通过*来获得block的值,因而在编译时,block的大小也不能被计算出来。

你可以创建一个block类型,当你在多个地方使用到同一个签名的block时,这种方式时很好的。

typedeffloat(^MyBlockType)(float,float);


MyBlockTypemyFirstBlock=//...;

MyBlockTypemySecondBlock=//...;

创建block

用^指明block语句的开始。在它后面的()是参数列表。block的主体部分在{}里面.。下面的例子定义了一个简单的block,并把先前定义的变量(oneForm)分配给它。

int(^oneFrom)(int);


oneFrom=^(intanInt){

returnanInt-1;

};

如果你不显式声明块表达式的返回值,它可以根据block的内容进行自动匹配。如果返回类型和参数列表都是void,你也可以省略参数列表。 如果存在多个返回语句,应该正确的进行匹配(又需要的话,可以使用类型转换)。

全局block

在文件级别,你可以使用block作为一个全局表达式。

#import<stdio.h>


intGlobalInt=0;

int(^getGlobalInt)(void)=^{returnGlobalInt;};

变量类型

block对象的代码中,变量被看成五种不同的方式。

与函数一样,block支持三种标准类型的变量:

全局变量,带有static修饰符的变量

全局函数(不是专门的变量)

局部变量和参数

Blocks也支持其他两种变量类型:

函数级别的_block变量。如有引用块被复制到堆,block中的_block变量是可变的。

const


最后,在一个方法的实现,块可能引用的Objective-C的实例变量—参考“Object
andBlockVariables.”

以下是在block中使用变量的规则:

可以访问全局变量,包括词法范围内存在的static变量。

可以传参给block,与传参给函数的方式是一样的。

局部词法范围内的堆栈变量被看成时const变量。

他们的值存放在程序内的block语句中。在嵌套block中,他们的值来自于最近的词法范围内。

声明为_block类型的
 局部词法范围内的变量是可改变的。更改的适用范围仅为局部词法范围,包括词法范围内定义的其他block。在“The
__blockStorageType.”中有更详细的描述。

block作用范围内声明的局部变量,与函数中的局部变量一样。

block的每一次调用,都重新生成此变量的新的副本。这些变量可以被转换为const或引用变量在块内的作用域。

下面的例子说明了局部非静态变量的使用:

intx=123;


void(^printXAndY)(int)=^(inty){


printf("%d%d\n",x,y);

};


printXAndY(456);//prints:123456

如上所述,在block内试图分配一个新的x的值将会报错:

intx=123;


void(^printXAndY)(int)=^(inty){


x=x+y;//error

printf("%d%d\n",x,y);

};

为了使一个变量在block内部可以被修改,你应该使用_block来修饰这个变量—参考 “The
__blockStorageType.”

_block存储类型

在变量前面加上_block类型修饰符,我们可以指定传进来的变量是可变或者可读写的。_block存储与它类似,但是局部变量的寄存器、auto变量和static存储类型之间相互排斥。

_block变量共享变量之间的作用域和块之间的作用域拷贝变量存储范围内。因此,如果block中定义的所有拷贝在框架内的生存超越帧结束(例如,正在排队等待执行),堆栈帧被破坏后存储也将继续存在
。在一个给定的词法范围的多个块,可以同时使用共享变量。

作为优化,在堆栈上的块存储块启动就像自身调用一样。如果块被复制,使用Block_copy(或在Objective-C中块发送一个副本),变量将被复制到堆。因此,_block块的地址可以随时更改。

_block变量有两个进一步的限制:他们不能是可变数组,不能包含C99的可变长数组的结构。

下面的例子说明了_block变量的作用:

__blockintx=123;//xlivesinblockstorage


void(^printXAndY)(int)=^(inty){


x=x+y;

printf("%d%d\n",x,y);

};

printXAndY(456);//prints:579456

//xisnow579

下面的例子显示了几种类型的变量块的相互作用:

externNSIntegerCounterGlobal;

staticNSIntegerCounterStatic;


{

NSIntegerlocalCounter=42;

__blockcharlocalCharacter;


void(^aBlock)(void)=^(void){

++CounterGlobal;

++CounterStatic;

CounterGlobal=localCounter;//localCounterfixedatblockcreation

localCharacter='a';//setslocalCharacterinenclosingscope

};


++localCounter;//unseenbytheblock

localCharacter='b';


aBlock();//executetheblock

//localCharacternow'a'

}

对象和block变量

Block提供支持的Objective-C和C++的对象,和其他块,作为变量。


Objective-C对象

在手动引用计数的环境,复制块时,块内使用局部变量保留。Block内使用的局部变量引用技术将retain。如果您想覆盖一个特定对象变量的这种行为,你可以标记_block修饰符来修饰该变量。

如果您使用ARC,当block被copy时对象变量被保留,并自动释放,和延迟释放。

注:在垃圾收集的环境,如果你给变量同时使用_weak和_block修饰符,那么该block将无法确保是否还存在。

如果你在执行方法内使用block,实例变量对象的内存管理规则更加微妙:

如果您访问实例变量的参照,对象retain;

如果您访问实例变量的值,对象retain;

下面的例子说明了两种不同的情况:

dispatch_async(queue,^{

//instanceVariableisusedbyreference,selfisretained

doSomethingWithObject(instanceVariable);

});



idlocalVariable=instanceVariable;

dispatch_async(queue,^{

//localVariableisusedbyvalue,localVariableisretained(notself)

doSomethingWithObject(localVariable);

});

调用block

如果你声明block作为一个变量,你可以像使用函数一样使用它,就像下面两个示例所示一样:

int(^oneFrom)(int)=^(intanInt){

returnanInt-1;

};


printf("1from10is%d",oneFrom(10));

//Prints"1from10is9"


float(^distanceTraveled)(float,float,float)=

^(floatstartingSpeed,floatacceleration,floattime){


floatdistance=(startingSpeed*time)+(0.5*acceleration*time*time);

returndistance;

};


floathowFar=distanceTraveled(0.0,9.8,1.0);

//howFar=4.9

然而,通常情况下,你使用block作为一个函数或方法的参数,在这些情况下,你通常创建一个块“内联”。

使用block作为函数参数

可以把block作为函数参数进行传递,就像其他参数一样。然后,很多时候你不需要声明block;而你只需把他们内联到需要使用block作为一个参数的地方。下面的例子使用了 
qsort_b方法, qsort_b方法与标准的qsort_r类似,只是用Block作为它的最后一个参数。


char*myCharacters[3]={"TomJohn","George","CharlesCondomine"};


qsort_b(myCharacters,3,sizeof(char*),^(constvoid*l,constvoid*r){

char*left=*(char**)l;

char*right=*(char**)r;

returnstrncmp(left,right,1);

});

//Blockimplementationendsat"}"


//myCharactersisnow{"CharlesCondomine","George","TomJohn"}

请注意,该块包含在函数的参数列表。

下面的示例显示如何使用block的dispatch_apply函数。 
dispatch_apply
使用以下方式进行定义:


voiddispatch_apply(size_titerations,dispatch_queue_tqueue,void(^block)(size_t));

功能是提交一个block到一个调度队列进行多次调用。它携带了三个参数;第一个参数指定执行的迭代的数量;第二个参数指定block被提交到哪个队列;点歌参数就是block自身,反过来这需要一个参数迭代的当前索引。

可以使用
dispatch_apply
 分别打印出迭代索引,如下所示:

#include<dispatch/dispatch.h>

size_tcount=10;

dispatch_queue_tqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);


dispatch_apply(count,queue,^(size_ti){

printf("%u\n",i);

});

使用block作为方法参数

Cocoa提供了一种方法,使用blocks。传递block作为参数与传递其他参数的方式是一样的。

下面的示例,确定一个数组前五个元素中任意一个在过滤集中的索引数。

NSArray*array=[NSArrayarrayWithObjects:@"A",@"B",@"C",@"A",@"B",@"Z",@"G",@"are",@"Q",nil];

NSSet*filterSet=[NSSetsetWithObjects:@"A",@"Z",@"Q",nil];


BOOL(^test)(idobj,NSUIntegeridx,BOOL*stop);


test=^(idobj,NSUIntegeridx,BOOL*stop){


if(idx<5){

if([filterSetcontainsObject:obj]){

returnYES;

}

}

returnNO;

};


NSIndexSet*indexes=[arrayindexesOfObjectsPassingTest:test];


NSLog(@"indexes:%@",indexes);


/*

Output:

indexes:<NSIndexSet:0x10236f0>[numberofindexes:2(in2ranges),indexes:(03)]

*/

下方的例子是确定一个NSSet对象中是否包含有局部变量指定的一个单词,如果包含的话,设置另一个局部变量的值为YES。found也被声明为一个_block变量, 这个block是定义联:

__blockBOOLfound=NO;

NSSet*aSet=[NSSetsetWithObjects:@"Alpha",@"Beta",@"Gamma",@"X",nil];

NSString*string=@"gamma";


[aSetenumerateObjectsUsingBlock:^(idobj,BOOL*stop){

if([objlocalizedCaseInsensitiveCompare:string]==NSOrderedSame){

*stop=YES;

found=YES;

}

}];


//Atthispoint,found==YES

复制block

通常情况下,你不需要复制(或保留)一个块。如果你想要block在它的定义域被销毁后仍可以被使用,你仅仅只需要创建一个副本。复制移动block到堆中。.

你能用C函数来复制和释放block:

Block_copy();

Block_release();

如果你使用Objective-C,block的属性可以使用copy、retain、release和autorelease。

为了避免产生内存泄露,block的copy和retain的使用必须平衡。使用了copy和retain的地方必须进行release(autorelease除外)——除非在一个垃圾收集环境。

避免的模式

块文本(即,^{...})是一个堆栈的本地数据结构的地址块。因此堆栈的本地数据结构的范围是封闭的复合语句,所以你应该避免使用下例中的模式:,

voiddontDoThis(){

void(^blockArray[3])(void); //anarrayof3blockreferences


for(inti=0;i<3;++i){

blockArray[i]=^{printf("hello,%d\n",i);};

//WRONG:Theblockliteralscopeisthe"for"loop

}

}


voiddontDoThisEither(){

void(^block)(void);


inti=random():

if(i>1000){

block=^{printf("gotiat:%d\n",i);};

//WRONG:Theblockliteralscopeisthe"then"clause

}

//...

}

调试

在block中你可以设置断点进行单步调试。你可以从调用块内GDB会议调用一个block,如下例所示:

$invoke-blockmyBlock1020

如果你想传一个C字符串值,你必须使用引用。例如,把这个字符串传到
doSomethingWithString
 block中,你可以像下面这样写:

$invoke-blockdoSomethingWithString"\"thisstring\""

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  iphone开发 ios开发 ios