分享

Block 编程(翻译官方文档)

 quasiceo 2015-03-05

注:小弟才疏学浅,英文水平够烂,若有不正确或误导的地方,请大家指出,欢迎大家指正和修改。本文中涉及的词法范围:作用范围,例如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;
};
image: ../Art/blocks.jpg

注意: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之所以能替代传统的回调方法主要有以下两个理由:

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

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

  2. 它允许访问局部变量。

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

声明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也支持其他两种变量类型:

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

  2. const

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

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

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

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

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

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

  4. 声明为_block类型的

    局部词法范围内的变量是可改变的。更改的适用范围仅为局部词法范围,包括词法范围内定义的其他block。 在“The __block Storage Type.”中有更详细的描述。
  5. 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\""


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多