主要内容:
1. 开始使用block(Getting Started with Blocks)
2. block概念综述(Conceptual Overview)
3. 声明和创建block(Declaring and Creating Blocks)
4. block和变量(Blocks and Variables)
5. 使用blocks(Using Blocks)
block介绍
block对象是C-level语句和运行时的特征,跟标准C函数有些相似;但是除了代码,它可以包含栈变量和堆变量。因此,当执行的时候,block可以维持一组状态(或者数据)。
开发者可以将block和函数表达式组合,可传递给API或者多线程。block尤其适用于回调,因为block携带了需要执行的的代码和数据。
block在GCC和Clang都是可用的。开发者可在OS X v10.6或者iOS 4.0之后使用。block
runtime是开源代码。在LLVM’s compiler-rt subprojct
repository中找到。block也被带入到C标准工程组,作为N1370:Apple’s Extensions to
C。因为OC和C++都源自C,所以,block被设计为三种语言下可工作的(还有Objective-C++),其语法反应这一目标。
开发者阅读这篇文档,了解block是什么,并了解怎么在C、C++和Objective-C使用它。
一、开始使用block
下面的几段代码帮助你开始使用block
声明并使用block
开发者使用^操作符声明block变量,^表示是一个block的开始。block的body体在{ }之内。例如:
- 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作为参数。
- charchar *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
- qsort_b(myCharacters, 3, sizeof(charchar *), ^(const voidvoid *l, const voidvoid *r) {
- charchar *left = *(charchar **)l;
- charchar *right = *(charchar **)r;
- return strncmp(left, right, 1);
- });
- // myCharacters is now { "Charles Condomine", "George", "TomJohn" }
Cocoa和block
好几个Cocoa框架的函数使用block作为参数,通常,block也是集合对象的操作或者当做一个回调。下面代码展示如何在NSArray方法
sortedArrayUsingComparator中使用block。该方法有只一个block参数。如图,block被定义为
NSComparator的本地变量。
- NSArray *stringsArray = @[ @"string 1",
- @"String 21",
- @"string 12",
- @"String 11",
- @"String 02" ];
- 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变量来计数多少个string被比较。如图,这种情况直接使用block并使用currentLocale作为一个只读变量。
- NSArray *stringsArray = @[ @"string 1",
- @"String 21", // <-
- @"string 12",
- @"String 11",
- @"Strîng 21", // <-
- @"Striñg 21", // <-
- @"String 02" ];
- 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和变量】。
二、概念综述
block 功能
● 参数列表,就像一个函数。
● 有inferred或者声明的返回类型
● 可获得义词法范围的状态,。
● 可选择性修改词法范围的状态。
● 可以用相同的词法范围内定义的其它block共享进行修改的可能性
● 可以继续共享和修改状态的词法范围内定义的(栈帧)词汇范围(栈帧)已被破坏
开发者可以copy block甚至传递它给其他线程延期执行。编译器核运行时系统整理:block引用的变量会被保护。尽管block在纯C和C++中可以,但是block仍是OC对象。
使用
block是一个较好得替代传统的callback函数,因为两个原因:
1. 允许你把写代码写在调用处,根据函数实现的上下文,该处稍后执行。因此,block经常作为框架的参数。
2. 允许访问本地变量。相比于callback需要一个数据结构封装所有上下文,block中可以直接访问本地变量。
三、声明和创建block
声明block引用
下面都是有效的声明:
- void (^blockReturningVoidWithVoidArgument)(void);
- int (^blockReturningIntWithIntAndCharArguments)(int, char);
- void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
block仍然支持可变参数(…)。没有参数的block必须指定void在参数列表。
开发者为block创建type,这样做通常是最好的做法。当在多个地方需要block签名
- typedef float (^MyBlockType)(float, float);
- MyBlockType myFirstBlock = // ... ;
- MyBlockType mySecondBlock = // ... ;
创建一个block
使用^表示block文字表达式的开始。它可能带()的参数列表跟着。block的body体包含在{}。下面例子定义一个简单block并把它赋值给一个提前定义好的变量(oneFrom),已C语句结束。
- float (^oneFrom)(float);
- oneFrom = ^(float aFloat) {
- float result = aFloat - 1.0;
- return result;
- };
如果你不显示声明blcok的返回值,它可以从该block的内容中自动推断。如果返回值被推断,参数列表是void,你也可以忽略(void)参数列表。如果当多个return语句都存在,它们必须完全匹配(有必要时使用cast)
全局block
At a file level, 开发者可以使用block
- #import <stdio.h>
- int GlobalInt = 0;
- int (^getGlobalInt)(void) = ^{ return GlobalInt; };
四、block和变量
变量类型
你可以引用三种不同类型的变量,就像从一个函数一样
● 全局变量,包括静态本地变量
● 全局函数
● 本地变量和参数,从一个封闭的范围
block也支持另外2两个类型:
1. 在功能的级别__block变量。他们是block内可变的和被保留,如果有任何的block被赋值到堆中。
2. const imports
最终,在一个方法的实现中。block可能引用OC的对象变量。参考“Object and Block Variables”
block中的变量,有以下五种不同的对待,下面的规则使用于block内的变量。
1. 全局变量,可以访问,包括封闭词法范围的静态变量。
2. 传递到block的参数是可访问的(就行函数的参数一样)。
3.
词法范围内的栈变量 ,被捕获为const变量。它们的值取在程序中的block表达式的地方。在嵌套block,该值是从最近的封闭范围内获得。
4. __block储修饰符声明的局部变量,是可变的。
任何更改都将反映在封闭的词法范围,包括相同封闭的词法范围内定义的任何其他块。
5. 定义在block范围内的本地变量,它的行为很像函数中的局部变。
每一次调用block将拷贝一份局部变量。这些变量可以反过来被用作常量或通过引用在块内的封闭块中的变量。
下面例子阐述了局部变量的用法:
- int x = 123;
- void (^printXAndY)(int) = ^(int y) {
- printf("%d %d\n", x, y);
- };
- printXAndY(456); // prints: 123 456
注意:试图给x赋新值将是错误的
- int x = 123;
- void (^printXAndY)(int) = ^(int y) {
- x = x + y; // error
- printf("%d %d\n", x, y);
- };
允许变量在block中可被访问,你可以使用__block存储类型修饰符。
__block存储类型
你可以指定一个imported的变量是可以修改的,通过__block存储类型修饰符。__block存储跟register、auto和static存储类型相似(但是之间互斥),用于局部变量。
__block变量驻在存储(变量的作用域,所有blocks,在变量作用域范围内声明或者创建Block 之间共享),因此,这个存储将会在栈结束被留下来。
作为优化,block存储在栈上,就像block本身那样。如果block被拷贝(通过Block_copy或者copy),变量被拷贝到堆。因此__block变量的地址就可能会改变。
__block变量还有两个限制,他们不能是变长数组,不能是结构体。
下面例子阐述了__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++对象的支持,和其他block、变量。
Objective-C 对象
当block被拷贝,它创建了block内变量的强引用。如果你在方法中使用block。
● 如果你访问实例变量通过引用,强引用self
● 如果你访问实例变量通过值,强应用该变量。
下面的例子阐述两种不同的情况:
- dispatch_async(queue, ^{
- // instanceVariable is used by reference, a strong reference is made to self
- doSomethingWithObject(instanceVariable);
- });
- id localVariable = instanceVariable;
- dispatch_async(queue, ^{
- /*
- localVariable is used by value, a strong reference is made to localVariable
- (and not to self).
- */
- doSomethingWithObject(localVariable);
- });
要覆盖一个特定的对象变量这个问题,您可以用__block存储类型修饰符标记。
C++对象
通常可以在block中使用C++对象。在成员函数中,成员变量的引用和函数需要通过this显示调用,因此也是可变的。有两个方面的考虑适用,如果一个block被复制
● 如果你有__block存储类的class,其将是一个基于堆栈的C++对象,那么通常的拷贝构造函数。
● 如果您使用任何其他的C++基于堆栈从一个块中的对象,它必须有一个const的拷贝构造函数。在C++对象,然后使用该构造函数复制。
block
当你拷贝一个block,从该block任何引用到其他功能block被复制,如果有必要。整个tree可能被复制。如果你有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作为函数参数,就像其他参数一样。多数情况,开发者不用声明一个block。相反你只需要实现inline的block作为参数,在需要的地方。下面的方法qsort_b使用block作为参数。
- charchar *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
- qsort_b(myCharacters, 3, sizeof(charchar *), ^(const voidvoid *l, const voidvoid *r) {
- charchar *left = *(charchar **)l;
- charchar *right = *(charchar **)r;
- return strncmp(left, right, 1);
- });
- // Block implementation ends at "}"
- // myCharacters is now { "Charles Condomine", "George", "TomJohn" }
- </pre></div><div>注意,该block包含在函数的参数列表中。下一个列子展示了在dispatch_apply函数中使用block,dispatch_apply是这么定义的:void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
该函数提交一个块到调度队列多次调用,它有三个参数:第一个指定迭代的数量。第二个参数指定block添加到的队列。第三是块本身,这又需要一个参数,当
前迭代的index。您可以使用dispatch_apply平凡刚打印出来的迭代指数,如下所
示:</div><div><pre code_snippet_id="114446" snippet_file_name="blog_20131216_17_728540" name="code" class="objc">#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提供了一系列的使用block的方法。开发者传一个block作为参数,就像其他参数一样。
下面的例子确定了“数组中的任意五个元素在给定的set中的索引”。
- NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];
- NSSet *filterSet = [NSSet setWithObjects: @"A", @"Z", @"Q", nil nil];
- BOOL (^test)(id obj, NSUInteger idx, BOOLBOOL *stop);
- test = ^(id obj, NSUInteger idx, BOOLBOOL *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也是__block修饰的。
- __block BOOL found = NO;
- NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil nil];
- NSString *string = @"gamma";
- [aSet enumerateObjectsUsingBlock:^(id obj, BOOLBOOL *stop) {
- if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
- *stop = YES;
- found = YES;
- }
- }];
- // At this point, found == YES
拷贝block
一般,开发者不需要copy(或者retain)block。你需要copy当你希望在声明的范围被析构后仍使用。copy block到堆上。
可以copy和release block用C函数
Block_copy();
Block_release();
为了避免内存泄露,开发者必须使用配对使用
Patterns to Avoid
block的文字段(即,^{…})是一个栈的本地数据结构,它表示该块的地址。因此栈的局部数据结构的范围是封闭的复合语句,所以你应该避免在下面的例子所示的图案:
- 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.
- }
- // ...
- }