最近的工做中比较频繁的用到了Block,不在是之前当作函数指针的替代或者某些API只有Blocks形式的接口才不得已用之了,发现本身对其了解仍是太浅,特别是变量的生存期,按惯例仍是翻译官方文档,原文连接。javascript
介绍
Block 对象是C语言层面的语法,也是一个运行时特性. 它们很相似与标准的C函数,可是除了可执行的代码,它们还包含了与自动(栈)或托管(堆)的内存所绑定的变量。所以一个block维护了一系列的状态(即数据),在执行时会改变代码的行为。html
你可使用blocks编写函数表达式当参数传入API,也能够将其保存下来用于多线程。Blocks在回调(回调的概念)中很是有用,由于block不只包含着回调时须要执行的代码,还包含了执行代码时须要的数据。java
你能够在Mac OS X 10.6和iOS 4.0以后的版本上的GCC附带的Clang上使用。blocks运行时库是开源的,见此 LLVM’s compiler-rt subproject repository.Blocks也已经呈现给了C标准工做组,见 N1370: Apple’s Extensions to C (其中包含了垃圾回收). 正如Objective-C和C++都是C的衍生语言,blocks也被设计成能够在这三种语言中使用 (如同Objective-C++). (语法也能够表现出其目标).ios
你应该阅读本文档来学习block的相关知识,以将block用于C,C++,Objective-C中,并提升你的程序的效率可可维护性。数组
文档结构
本文档包括如下章节:安全
-
"Blocks入门" 提供了快速,使用的blocks简介数据结构
-
“总体概念” 提供了blocks在概念上的介绍多线程
-
"声明和建立Blocks" 为您展现如何声明block变量及实现blocks闭包
-
"Blocks和变量" 描述blocks和变量之间的相互做用, __block
修饰符的做用app
-
"使用Blocks" 详解各类用法范式
Blocks入门
下面章节使用实际的例子帮助您入门blocks
声明和使用Block
你要使用^操做符去声明一个block变量,^也是标示着一段block文字的开始。block的实体包含在{}中,以下所示(形同C,;表示语句的终结):
- int multiplier = 7;
- int (^myBlock)(int) = ^(int num) {
- return num * multiplier;
- };
示例的详解以下(图片很差翻译凑合着看):
注意block可使用其定义范围内的变量.
若是你把block声明为一个变量,你能够把它当一个函数(function,本文中特指C语言形式的函数)同样调用:
- int multiplier = 7;
- int (^myBlock)(int) = ^(int num) {
- return num * multiplier;
- };
-
- printf("%d", myBlock(3));
- // prints "21"
直接使用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);
- });
-
- // myCharacters is now { "Charles Condomine", "George", "TomJohn" }
Cocos中的Blocks
许多 Cocoa frameworks 中的方法(method,特指Objecitve-C的方法即[])使用block做为参数, 常见于对集合中对象的操做或一个操做完成以后的回调. 下例展现和如何在NSArray
的方法 sortedArrayUsingComparator:
中使用block。该方法使用一个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变量
blocks有一个强大的特性,即它能够修改其当前词法范围内的变量. 只要对变量加上 __block
存储修饰符. 稍微修改上面的例子, 使用一个block变量去统计下例中有多少字符串是相同的。例子中的block仍是直接使用的,并用到了叫 currentLocale
的只读变量:
- 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
- */
Block对象提供了建立特殊函数的方法,函数体可用C,Objective-C,C++等C类的语言作表达式. 在其余的语言环境中,block变量可能会被叫作"闭包(closure)", 而在这里,除非和标准C术语的一段(block)代码混淆的状况以外,通常称为"blocks"。
Block 功能性
一个block就是一块匿名的代码块:
- 和函数同样有含类型的参数列表
- 有直接声明或可推断出的返回值
- 能够得到当前词法范围的状态
- 有能力修改当前词法范围的状态
- 能够和当前词法范围的其余block共同修改状态
- 能够持续共享和修改当前词法范围的状态,甚至在当前词法范围销毁以后
你能够copy一个block还能够将其传到别的线程以延后执行 (或本线程的执行循环里). 编译器和运行时会把block里用到的全部变量保存到该block的全部拷贝的生存期后。尽管blocks能够由纯C或C++写成,但block自己始终是一个Objective-C变量.
用法
Blocks通常都是小段的,自成体系的代码块. 所以,特别适合用在封转并行操做所需的数据,或用于集合中,以及操做完成后的回调.
Blocks基于两大理由,是传统回调函数的优秀替代品:
-
容许你把具体实现代码写在调用该方法的地方.
Blocks也常常是framework的方法参数.
- 能够访存局部变量. 不须要像之前的回调同样,把在操做后全部须要用到的数据封装成特定的数据结构, 你彻底能够直接访问局部变量.
声明和建立Blocks
声明Block引用
Block变量保持blocks的引用. 声明block的语法相似于声明函数指针,区别之处在于使用^而不是*.block的类型取决于C的类型系统. 下面都是有效的block变量声明:
- void (^blockReturningVoidWithVoidArgument)(void);
- int (^blockReturningIntWithIntAndCharArguments)(int, char);
- void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
Blokcs也支持可变参数即(...). 若是不带参数则必须在参数列表中指定 void
.
Blocks被设计为彻底的类型安全,编译器可由一整套的元数据去判断blocks,blocks的参数,及返回值的有效性(这句没太看懂,原文是Blocks are designed to be fully type safe by giving the compiler a full set of metadata to use to validate use of blocks, parameters passed to blocks, and assignment of the return value)。 你能够把block强制转换为任意类型的指针,反之亦然。不过,你不能像对指针同样用*运算符对block进行解引用(dereference)操做,由于block的大小没法在编译时算出.
你也能够建立blocks的类型,在几个地方用到一样函数签名的block时,这是一种很好的作法:
- typedef float (^MyBlockType)(float, float);
-
- MyBlockType myFirstBlock = // ... ;
- MyBlockType mySecondBlock = // ... ;
建立Block
使用 ^
操做符标示出block表达式的字面开始部分. 后面跟随着包含参数列表的().block主体则是包含在{}之中。 下例展现了如何定义一个简单的block,并将其赋值给以前定义的变量 (oneFrom
).最后由常规的 ;
也是c语言的语句结束符结束
- int (^oneFrom)(int);
-
- oneFrom = ^(int anInt) {
- return anInt - 1;
- };
若是你不想显式的声明block的返回值,也能够由block内容自动推断出来。若是返回值是可推断的,而且参数列表为 void
, 你也能够省略参数列表. 当有多个返回语句时,它们必须是类型一直的(必要时会进行强行转换).
全局Blocks
在文件层面上,你可使用block当全局的字面文字:
- #import <stdio.h>
-
- int GlobalInt = 0;
- <p>int (^getGlobalInt)(void) = ^{ return GlobalInt; };</p>
Blocks和变量
本部分阐述blocks和变量之间的交互做用,包括内存管理.
变量的类型
在block的主体代码块中,变量能够分为五种.
你能够引用三种标准类型的变量,就如同函数传参:
Blocks还支持另外两种类型的变量:
-
在函数级别的 __block
变量. 它们在block中是可变的 (同时也在做用域中可变),若是有block被拷贝到了堆(heap)上,则它们也会被保存.
-
const
imports.
最后,在一个方法的具体实现中,blocks能够引用Objective-C的实体变量,见 “对象和Block变量.”
block使用变量适用以下规则:
-
全局变量是可访存的,包括做用域内的静态变量.
-
block的参数是可访存的 (和函数的参数同样).
-
做用域内的局部栈(非静态)变量被当作 const
变量.
它们的值以block表达式在程序的点为准. 在嵌套blocks中,则是里该block最近的做用域中的值为准.
-
词法做用域中的局部变量有 __block
存储修饰符的,是按引用传递并能够修改.
全部的变更都反应到做用域中,包括其余在本做用域内定义的block中作的修改.
详见 “__block 存储类型.”
-
在block内部声明的局部变量,就和函数内声明的变量同样.
每次调用block都产生变量的新的拷贝,这些变量轮流被当作 const
或按引用传递的变量用在block内部.
下例使用局部非静态变量:
- 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
类型的存储修饰符,详见“ __block 存储类型.”
The __block Storage Type
你能够指明一个导入的变量为可变的便可读写的,只须要使用 __block
类型存储修饰符. __block
存储类型类不一样于 register
, auto
, 但和static
存储类型同样,对于局部变量提供了可变的能力.
__block
变量生存于存储区内,并供当前词法范围的全部blocks共享使用. 所以,该存储区将存活到block栈frame销毁以后,甚至在其余拷贝或声明了这些block的block销毁后 (好比压到队列中供后续执行). 在给定的词法范围里的多个blocks能够同时使用一个共享的变量.(这段我整个没看懂,原文是__block
variables live in storage that is shared between the lexical scope of the variable and all blocks and block copies declared or created within the variable’s lexical scope. Thus, the storage will survive the destruction of the stack frame if any copies of the blocks declared within the frame survive beyond the end of the frame (for example, by being enqueued somewhere for later execution). Multiple blocks in a given lexical scope can simultaneously use a shared variable.)
做为优化, block变量和block自己同样开始是存储在栈上. 但若是用Block_copy
(若是是Objecitve-C环境下, 能够直接向block发送 copy
)对block进行拷贝, 变量就会拷贝到堆上. 所以 __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
下例展现几种变量和blocks之间的交互:
- 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变量
Blocks支持Objecitve-C和C++对象,还包括其余的能够当作变量的blocks.
Objective-C对象
在引用计数的环境下,默认状况若是你引用了一个Objective-C对象,它将会被retain,即便你只是使用了这个对象的实体变量. 若是对象使用了 __block
存储修饰符,则不会被retain.
Note: 在垃圾回收的环境下,若是你对变量同时使用了 __weak
和 __block
修饰符, block将不会保证其生存期.
若是你在方法中使用了block,而且使用到了对象的实体变量,那么内存管理的规则将会更微妙:
-
若是你使用了改实体变量的引用,则 self
将被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);
- });
C++对象
你能够在block内使用C++变量. 在成员函数中,对成员变量和函数的引用实际上都是隐式使用了 this
指针,故而都是可变的. 当block被拷贝的时候,有两种状况须要留心:
-
若是你使用的是栈基(stack-based)的C++变量,而且有 __block
存储修饰符, 一般使用拷贝构造函数来构造对象.
-
除此之外的栈基C++对象, 则必需要有const拷贝构造函数(形如 Object(const Object&o)),拷贝这些对象时将使用该方法.
Blocks
当拷贝一个block,该block内全部引用到的其余block都将被拷贝,若是改block使用到的block变量里引用到了别的block,则别的block也将被拷贝.
当拷贝一个栈基block,你将获得一个新block. 若是拷贝一个堆基的block,则仅仅是增长其引用计数,在拷贝函数和方法返回后还会降回去.
使用Blocks
调用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当参数传给函数或者方法.某些场合,你也能够"内联(inline)"的建立block.
使用Block做为函数参数
你能够传一个block当参数给函数,就和其余类型的参数同样. 不少状况,你都不必声明blocks, 而是简单的在哪里须要就哪里直接建立. 下例展现使用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
函数中使用block. 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
对象是否包含了一个局部变量,并设置另外一个局部变量(found
),在查找到的状况下 为 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
拷贝Blocks
通常而言,你没有表去copy(或retain)一个block. 除非你但愿使用该block在当前声明的范围销毁以后. 拷贝会将block移到堆(heap)中.
你可使用C函数去拷贝或释放blocks:
- Block_copy();
- Block_release();
若是你正在使用Objective-C, 你能够向block对象发送
copy
,
retain
, 和
release
(也包括
autorelease
) 消息.
为了不内存泄露, Block_copy()
和Block_release()
必需要对应使用. 一样的也要对应 copy
或 retain
和 release
(或 autorelease
)的使用.除非是在垃圾回收的环境.
应避免的作法
block文本(即, ^{ ... }
) 是表示block的局部栈数据结构(stack-local data structure)的地址. 因此这些栈数据仅在当前声明的范围内有效,必须避免以下的使用:
- 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
- }
- // ...
- }
调试
你能够在blocks中设断点并单步跟踪. 你也能够在GDB里直接用 invoke-block
命令调用blocks,以下所示:
- $ invoke-block myBlock 10 20
若是要传递C的字符串,你必须用引用括起来, 好比把
this string
传给
doSomethingWithString
block, 得这么写:
- $ invoke-block doSomethingWithString "\"this string\""