转载请附上原文链接: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.6 Xcode开发工具附带的工具,Block在GCC和Clang中同样可用。你能在Mac OS X v10.6 及其以上版本和iOS 4.0及其以上版本中使用Block。.Block的运行是开源的,因此你能在LLVM’s compiler-rt subproject repository里面找到它。Block也已经被提交到C标准工作组作为 N1370: Apple’s Extensions to C。 由于Objective-C 和 C++ 都是衍生自 C,block被设计为可同时兼容这三种语言。
你应该阅读这篇文档,去学习Block是什么,以及在C、C++和OC中如何使用Block使你的程序更加的高效和更易于维护。
声明和使用Block
用^操作符来声明一个Block变量,并指明Block述句的开始。Block的主体部分包含在 {}内,像下面的例子中一样(与C语法一样,“;”指明语句的结束):
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
注意:Block可以使用定义范围之内的任何变量。
如果你把Block声明为一个变量,你以后就可以像调用一个方法一样使用它:
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
printf("%d", myBlock(3));
// prints "21"
直接使用Block
很多情况下,你不需要声明Block变量;你只是简单地写一个Block语句内联在需要使用它作为参数的地方。下面的例子使用了 qsort_b方法, qsort_b方法与标准的qsort_r类似,只是用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;
return strncmp(left, right, 1);
});
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
Cocoa Block
Cocoa框架中有几个方法使用Block作为参数,通常是在执行对象的操作集合,或者操作完成后使用它作为回调。下面的例子向我们展示了
在NSArray 对象的方法sortedArrayUsingComparator:怎样使用Block.。这个方法只有单一的参数,block被定义为NSComparator 局部变量:
NSArray *stringsArray = [NSArray arrayWithObjects:
@"string 1",
@"String 21",
@"string 12",
@"String 11",
@"String 02", nil];
static NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch | NSNumericSearch |
NSWidthInsensitiveSearch | NSForcedOrderingSearch;
NSLocale *currentLocale = [NSLocale currentLocale];
NSComparator finderSortBlock = ^(id string1, id string2) {
NSRange string1Range = NSMakeRange(0, [string1 length]);
return [string1 compare:string2 options:comparisonOptions range:string1Range locale:currentLocale];
};
NSArray *finderSortArray = [stringsArray sortedArrayUsingComparator:finderSortBlock];
NSLog(@"finderSortArray: %@", finderSortArray);
/*
Output:
finderSortArray: (
"string 1",
"String 02",
"String 11",
"string 12",
"String 21"
)
*/
_block变量
Block的一个强大的特性是,它能在相同的词法范围内修改变量值。Block能修改变量是通过_block存储类型标示符。与 “Cocoa Block” 中的例子一样,你能使用一个block变量来计算有多少字符串与下面例子中是相同的。 Block直接被用,并且用 currentLocale作为一个只读变量在block中。
NSArray *stringsArray = [NSArray arrayWithObjects:
@"string 1",
@"String 21", // <-
@"string 12",
@"String 11",
@"Strîng 21", // <-
@"Striñg 21", // <-
@"String 02", nil];
NSLocale *currentLocale = [NSLocale currentLocale];
__block NSUInteger orderedSameCount = 0;
NSArray *diacriticInsensitiveSortArray = [stringsArray sortedArrayUsingComparator:^(id string1, id string2) {
NSRange string1Range = NSMakeRange(0, [string1 length]);
NSComparisonResult comparisonResult = [string1 compare:string2 options:NSDiacriticInsensitiveSearch range:string1Range locale:currentLocale];
if (comparisonResult == NSOrderedSame) {
orderedSameCount++;
}
return comparisonResult;
}];
NSLog(@"diacriticInsensitiveSortArray: %@", diacriticInsensitiveSortArray);
NSLog(@"orderedSameCount: %d", orderedSameCount);
/*
Output:
diacriticInsensitiveSortArray: (
"String 02",
"string 1",
"String 11",
"string 12",
"String 21",
"Str\U00eeng 21",
"Stri\U00f1g 21"
)
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时,这种方式时很好的。
typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;
创建block
用^指明block语句的开始。在它后面的()是参数列表。block的主体部分在{ }里面.。下面的例子定义了一个简单的block,并把先前定义的变量(oneForm)分配给它。
int (^oneFrom)(int);
oneFrom = ^(int anInt) {
return anInt - 1;
};
如果你不显式声明块表达式的返回值,它可以根据block的内容进行自动匹配。如果返回类型和参数列表都是void,你也可以省略参数列表。 如果存在多个返回语句,应该正确的进行匹配(又需要的话,可以使用类型转换)。
全局block
在文件级别,你可以使用block作为一个全局表达式。
#import <stdio.h>
int GlobalInt = 0;
int (^getGlobalInt)(void) = ^{ return GlobalInt; };
变量类型
block对象的代码中,变量被看成五种不同的方式。
与函数一样,block支持三种标准类型的变量:
全局变量,带有static修饰符的变量
全局函数(不是专门的变量)
局部变量和参数
Blocks也支持其他两种变量类型:
函数级别的_block变量。如有引用块被复制到堆,block中的_block变量是可变的。
const
最后,在一个方法的实现,块可能引用的Objective-C的实例变量—参考“Object and Block Variables.”
以下是在block中使用变量的规则:
可以访问全局变量,包括词法范围内存在的static变量。
可以传参给block,与传参给函数的方式是一样的。
局部词法范围内的堆栈变量被看成时const变量。
他们的值存放在程序内的block语句中。 在嵌套block中,他们的值来自于最近的词法范围内。
声明为_block类型的 局部词法范围内的变量是可改变的。更改的适用范围仅为局部词法范围,包括词法范围内定义的其他block。 在“The __block Storage Type.”中有更详细的描述。
block作用范围内声明的局部变量,与函数中的局部变量一样。
block的每一次调用,都重新生成此变量的新的副本。这些变量可以被转换为const或引用变量在块内的作用域。
下面的例子说明了局部非静态变量的使用:
int x = 123;
void (^printXAndY)(int) = ^(int y) {
printf("%d %d\n", x, y);
};
printXAndY(456); // prints: 123 456
如上所述,在block内试图分配一个新的x的值将会报错:
int x = 123;
void (^printXAndY)(int) = ^(int y) {
x = x + y; // error
printf("%d %d\n", x, y);
};
为了使一个变量在block内部可以被修改,你应该使用_block来修饰这个变量—参考 “The __block Storage Type.”
_block存储类型
在变量前面加上_block类型修饰符,我们可以指定传进来的变量是可变或者可读写的。_block存储与它类似,但是局部变量的寄存器、auto变量和static存储类型之间相互排斥。
_block变量共享变量之间的作用域和块之间的作用域拷贝变量存储范围内。因此,如果block中定义的所有拷贝在框架内的生存超越帧结束(例如,正在排队等待执行),堆栈帧被破坏后存储也将继续存在。在一个给定的词法范围的多个块,可以同时使用共享变量。
作为优化,在堆栈上的块存储块启动就像自身调用一样。如果块被复制,使用Block_copy(或在Objective-C中块发送一个副本),变量将被复制到堆。因此,_block块的地址可以随时更改。
_block变量有两个进一步的限制:他们不能是可变数组,不能包含C99的可变长数组的结构。
下面的例子说明了_block变量的作用:
__block int x = 123; // x lives in block storage
void (^printXAndY)(int) = ^(int y) {
x = x + y;
printf("%d %d\n", x, y);
};
printXAndY(456); // prints: 579 456
// x is now 579
下面的例子显示了几种类型的变量块的相互作用:
extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
NSInteger localCounter = 42;
__block char localCharacter;
void (^aBlock)(void) = ^(void) {
++CounterGlobal;
++CounterStatic;
CounterGlobal = localCounter; // localCounter fixed at block creation
localCharacter = 'a'; // sets localCharacter in enclosing scope
};
++localCounter; // unseen by the block
localCharacter = 'b';
aBlock(); // execute the block
// localCharacter now 'a'
}
对象和block变量
Block提供支持的Objective-C和C+ +的对象,和其他块,作为变量。
Objective-C 对象
在手动引用计数的环境, 复制块时,块内使用局部变量保留。Block内使用的局部变量引用技术将retain。如果您想覆盖一个特定对象变量的这种行为,你可以标记_block修饰符来修饰该变量。
如果您使用ARC,当block被copy时对象变量被保留,并自动释放,和延迟释放。
注:在垃圾收集的环境,如果你给变量同时使用_weak和_block修饰符,那么该block将无法确保是否还存在。
如果你在执行方法内使用block,实例变量对象的内存管理规则更加微妙:
如果您访问实例变量的参照,对象retain;
如果您访问实例变量的值,对象retain;
下面的例子说明了两种不同的情况:
dispatch_async(queue, ^{
// instanceVariable is used by reference, self is retained
doSomethingWithObject(instanceVariable);
});
id localVariable = instanceVariable;
dispatch_async(queue, ^{
// localVariable is used by value, localVariable is retained (not self)
doSomethingWithObject(localVariable);
});
调用block
如果你声明block作为一个变量,你可以像使用函数一样使用它,就像下面两个示例所示一样:
int (^oneFrom)(int) = ^(int anInt) {
return anInt - 1;
};
printf("1 from 10 is %d", oneFrom(10));
// Prints "1 from 10 is 9"
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作为函数参数进行传递,就像其他参数一样。然后,很多时候你不需要声明block;而你只需把他们内联到需要使用block作为一个参数的地方。下面的例子使用了 qsort_b方法, qsort_b方法与标准的qsort_r类似,只是用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;
return strncmp(left, right, 1);
});
// Block implementation ends at "}"
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
请注意,该块包含在函数的参数列表。
下面的示例显示如何使用block的dispatch_apply函数。 dispatch_apply使用以下方式进行定义:
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
功能是提交一个block到一个调度队列进行多次调用。它携带了三个参数;第一个参数指定执行的迭代的数量;第二个参数指定block被提交到哪个队列; 点歌参数就是block自身,反过来这需要一个参数迭代的当前索引。
可以使用dispatch_apply 分别打印出迭代索引,如下所示:
#include <dispatch/dispatch.h>
size_t count = 10;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
printf("%u\n", i);
});
使用block作为方法参数
Cocoa提供了一种方法,使用blocks。传递block作为参数与传递其他参数的方式是一样的。
下面的示例,确定一个数组前五个元素中任意一个在过滤集中的索引数。
NSArray *array = [NSArray arrayWithObjects: @"A", @"B", @"C", @"A", @"B", @"Z",@"G", @"are", @"Q", nil];
NSSet *filterSet = [NSSet setWithObjects: @"A", @"Z", @"Q", nil];
BOOL (^test)(id obj, NSUInteger idx, BOOL *stop);
test = ^ (id obj, NSUInteger idx, BOOL *stop) {
if (idx < 5) {
if ([filterSet containsObject: obj]) {
return YES;
}
}
return NO;
};
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:test];
NSLog(@"indexes: %@", indexes);
/*
Output:
indexes: <NSIndexSet: 0x10236f0>[number of indexes: 2 (in 2 ranges), indexes: (0 3)]
*/
下方的例子是确定一个NSSet对象中是否包含有局部变量指定的一个单词,如果包含的话,设置另一个局部变量的值为YES。found也被声明为一个_block变量, 这个block是定义联:
__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";
[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
*stop = YES;
found = YES;
}
}];
// At this point, 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除外)——除非在一个垃圾收集环境。
避免的模式
块文本(即,^{...})是一个堆栈的本地数据结构的地址块。因此堆栈的本地数据结构的范围是封闭的复合语句,所以你应该避免使用下例中的模式:,
void dontDoThis() {
void (^blockArray[3])(void); // an array of 3 block references
for (int i = 0; i < 3; ++i) {
blockArray[i] = ^{ printf("hello, %d\n", i); };
// WRONG: The block literal scope is the "for" loop
}
}
void dontDoThisEither() {
void (^block)(void);
int i = random():
if (i > 1000) {
block = ^{ printf("got i at: %d\n", i); };
// WRONG: The block literal scope is the "then" clause
}
// ...
}
调试
在block中你可以设置断点进行单步调试。你可以从调用块内GDB会议调用一个block,如下例所示:
$ invoke-block myBlock 10 20
如果你想传一个C字符串值,你必须使用引用。例如, 把这个字符串传到doSomethingWithString block中,你可以像下面这样写:
$ invoke-block doSomethingWithString "\"this string\""