Objective-C 苹果开发文档 08 Working with Blocks

Working with Blocks

OC中一个类定义了一个对象,就是将数据和相关的方法结合到一起。有时候,与其直接使用一些方法,不如只是表示一个单一的任务或者行为单元。

Block是添加到C,C++,和OC中的语言级别的特性,她允许你创建不同的代码段,可以像使用变量那样传递方法或者函数。block是OC的对象,这意味着她们可以被添加到像NSArray或者NSDictionary这样的集合中。她们也可以在闭合的范围内捕获值,就像其他语言中的闭包或者匿名。

本章会介绍声明和引用block的语法,以及如何使用block简化普通的任务,比如集合的枚举。详见 Blocks Programming Topics


Block Syntax

The syntax to define a block literal uses the caret symbol (^), like this:

定义一个block的字面语法是使用符号^,像这样:

    ^{
         NSLog(@"This is a block");
    }

As with function and method definitions, the braces indicate the start and end of the block. In this example, the block doesn’t return any value, and doesn’t take any arguments.

与函数和方法的定义一样,大括号表示block的开始和结束。本例中,block并没有返回值,也没有任何参数。

你可以使用一个函数指针引用一个函数,同样,你可以声明一个变量追踪一个block,像这样:

In the same way that you can use a function pointer to refer to a C function, you can declare a variable to keep track of a block, like this:

    void (^simpleBlock)(void);

If you’re not used to dealing with C function pointers, the syntax may seem a little unusual. This example declares a variable called simpleBlock to refer to a block that takes no arguments and doesn’t return a value, which means the variable can be assigned the block literal shown above, like this:

如果你不习惯处理C函数指针,语法看起来会有点不同。这个例子中声明了一个变量simpleBlock,用来引用一个无参无返回值的block,这意味着变量可以被分配为block:

    simpleBlock = ^{
        NSLog(@"This is a block");
    };

这就像是其他变量的分配一样,因此声明必须以大括号之后的分号结尾。你还可以将变量的声明和分配结合在一起:

    void (^simpleBlock)(void) = ^{
        NSLog(@"This is a block");
    };

Once you’ve declared and assigned a block variable, you can use it to invoke the block:

一旦你已经声明分配了一个block变量,你就可以使用这个变量调用block:

    simpleBlock();

Note: 如果你试图用一个未被分配值的变量(一个nil block变量)调用一个block程序块,你的app会崩溃的哦

Blocks Take Arguments and Return Values


程序块也可以有参数和返回值,就像方法和函数那样。

例如,一个变量引用一个程序块,返回值是两个变量的乘积。

    double (^multiplyTwoValues)(double, double);

The corresponding block literal might look like this:

相应的程序块字面描述看起来可能是这样的:

    ^ (double firstValue, double secondValue) {
        return firstValue * secondValue;
    }

The firstValue and secondValue are used to refer to the values supplied when the block is invoked, just like any function definition. In this example, the return type is inferred from the return statement inside the block.

当block被调用的时候,firstValue和secondValue被用来引用提供的值,就像其他的函数定义一样。本例中,返回值类型的推断依据是block内部的返回类型。

如果你喜欢,你可以显示的指定返回值类型在插入符合参数列表之间:

    ^ double (double firstValue, double secondValue) {
        return firstValue * secondValue;
    }

Once you’ve declared and defined the block, you can invoke it just like you would a function:

一旦你声明定义了block,你可以想调用函数那样使用她:

    double (^multiplyTwoValues)(double, double) =
                              ^(double firstValue, double secondValue) {
                                  return firstValue * secondValue;
                              };
 
    double result = multiplyTwoValues(2,4);
 
    NSLog(@"The result is %f", result);

Blocks Can Capture Values from the Enclosing Scope

As well as containing executable code, a block also has the ability to capture state from its enclosing scope.

 和包含可执行代码一样,一个block也可以在她的作用域中捕获状态。

如果你在方法的内部声明一个代码块,那么可能在方法内部捕获任何的值,像这样:

If you declare a block literal from within a method, for example, it’s possible to capture any of the values accessible within the scope of that method, like this:

- (void)testMethod {
    int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    testBlock();
}

In this example, anInteger is declared outside of the block, but the value is captured when the block is defined.

本例中,anInteger是在block外部声明的,但是值是在block定义的时候捕获的。

除非你指定其他的,不然只能捕获值。这意味着如果你想在定义block和调用之间改变外部的变量值:

Only the value is captured, unless you specify otherwise. This means that if you change the external value of the variable between the time you define the block and the time it’s invoked, like this:

    int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    anInteger = 84;
 
    testBlock();

block捕获的值不会受到影响。这意味着日志输出仍然是这样的:

Integer is: 42

It also means that the block cannot change the value of the original variable, or even the captured value (it’s captured as a const variable).

这同样意味着block不能改变原始的变量值看,或者捕获改变的值(捕获值被视为const常量)

Use __block Variables to Share Storage

If you need to be able to change the value of a captured variable from within a block, you can use the __block storage type modifier on the original variable declaration. This means that the variable lives in storage that is shared between the lexical scope of the original variable and any blocks declared within that scope.

如果你需要改变block内部捕获的值,你可以使用__block存储类型修饰符在原始变量的声明上。这意味着存储的变量是可以在这个词的原始变量范围内与block声明的范围之间共享的。

例如,您可以重写前面的例子:

    __block int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    anInteger = 84;
 
    testBlock();

Because anInteger is declared as a __block variable, its storage is shared with the block declaration. This means that the log output would now show:

Integer is: 84

It also means that the block can modify the original value, like this:

    __block int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
        anInteger = 100;
    };
 
    testBlock();
    NSLog(@"Value of original variable is now: %i", anInteger);

This time, the output would show:

Integer is: 42
Value of original variable is now: 100

You Can Pass Blocks as Arguments to Methods or Functions

前面的每个例子都是在声明block之后直接调用,但是实际情况中,普遍的做法是传递block给函数或者方法作为在别处的调用列表。例如,你可以在后台使用GCD(并且多线程优化)技术调用一个block,或者定义一个block表示一个反复调用的任务,比如当要遍历一个集合的时候。稍后会讲到并发和枚举器。

block也可以用来作为回调函数,定义的代码会在一个任务结束的时候执行。例如,你的应用程序可能需要响应用户操作通过创建一个对象,执行一个复杂的任务,比如从一个web服务请求信息。因为任务可能花费很长时间,所以当时事件发生时,应该显示一个进度条,一旦任务完成,隐藏这个指示器。

你可以使用代理来完成这个功能,你需要创建一个合适的代理协议,然后实现必须实现的方法,设置你的对象为代理的委托任务,之后一旦任务结束,你的对象调用代理方法。

block可以让这一切变的更容易,因为你可以在开始任务的时候定义回调行为,像这样:

- (IBAction)fetchRemoteInformation:(id)sender {
    [self showProgressIndicator];
 
    XYZWebTask *task = ...
 
    [task beginTaskWithCallbackBlock:^{
        [self hideProgressIndicator];
    }];
}

This example calls a method to display the progress indicator, then creates the task and tells it to start. The callback block specifies the code to be executed once the task completes; in this case, it simply calls a method to hide the progress indicator. Note that this callback block captures self in order to be able to call the hideProgressIndicator method when invoked. It’s important to take care when capturing self because it’s easy to create a strong reference cycle, as described later in 

本例中调用了一个方法来显示进度指示器的功能,然后创建任务并且开始执行。block回调方法指定了被执行的代码一旦任务完成的时候,本例中,只是简单的调用一个方法来隐藏进度指示器。注意,回调的block捕获self是为了能够在自己被调用的时候调用hideProgressIndicator方法。当捕获self的时候需要注意的是很容易创建一个强引用循环,详见Avoid Strong Reference Cycles when Capturing self

从代码的可读性上看,block可以很容易的知道一个位置在任务完成的之前或者之后具体会发生什么,避免了需要通过委托方法跟踪看看会发生什么。

The declaration for the beginTaskWithCallbackBlock: method shown in this example would look like this:

- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock;

The (void (^)(void)) specifies that the parameter is a block that doesn’t take any arguments or return any values. The implementation of the method can invoke the block in the usual way:

- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
    ...
    callbackBlock();
}

Method parameters that expect a block with one or more arguments are specified in the same way as with a block variable:

- (void)doSomethingWithBlock:(void (^)(double, double))block {
    ...
    block(21.0, 2.0);
}
A Block Should Always Be the Last Argument to a Method

It’s best practice to use only one block argument to a method. If the method also needs other non-block arguments, the block should come last:

惯例是一个方法只使用一个block参数。如果方法需要另一个非block参数,那么block参数应该是在最后:

- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback;

This makes the method call easier to read when specifying the block inline, like this:

当定义内联的block时,使得方法的调用更容易阅读,像这样:

    [self beginTaskWithName:@"MyTask" completion:^{
        NSLog(@"The task is complete");
    }];

Use Type Definitions to Simplify Block Syntax

If you need to define more than one block with the same signature, you might like to define your own type for that signature.

如果你需要定义不止一个block使用同样的签名,你可以定义为签名定义自己的类型。

例如,你可以为一个简单的block定义一个类型,没有参数和返回值,像这样:

As an example, you can define a type for a simple block with no arguments or return value, like this:

typedef void (^XYZSimpleBlock)(void);

You can then use your custom type for method parameters or when creating block variables:

    XYZSimpleBlock anotherBlock = ^{
        ...
    };
- (void)beginFetchWithCallbackBlock:(XYZSimpleBlock)callbackBlock {
    ...
    callbackBlock();
}

Custom type definitions are particularly useful when dealing with blocks that return blocks or take other blocks as arguments. Consider the following example:

void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
    ...
    return ^{
        ...
    };
};

The complexBlock variable refers to a block that takes another block as an argument (aBlock) and returns yet another block.

Rewriting the code to use a type definition makes this much more readable:

XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) {
    ...
    return ^{
        ...
    };
};

Objects Use Properties to Keep Track of Blocks


定义一个属性来追踪block的语法和定义block变量相似:

@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end

Note:  你应该制定copy作为属性参数,因为一个block需要副本来追踪她在原始范围以外捕获的状态。当你使用ARC的时候不需要担心这回事,因为她是自动执行的,但是惯例是这样的,详见Blocks Programming Topics

A block property is set or invoked like any other block variable:

    self.blockProperty = ^{
        ...
    };
    self.blockProperty();

It’s also possible to use type definitions for block property declarations, like this:

typedef void (^XYZSimpleBlock)(void);
 
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end

Avoid Strong Reference Cycles when Capturing self


如果你需要在block中捕获self,比如你在定义一个回调block的时候,那么考虑到内存管理的影响很重要。

block对于任何捕获的对象都是强引用,包括self,这就意味着如果一个对象包含copy属性的block,在捕获self的时候就很容易以一个强引用循环儿终止运行:

@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
    self.block = ^{
        [self doSomething];    // capturing a strong reference to self
                               // creates a strong reference cycle
    };
}
...
@end

在这个例子中编译器会警告,但是在更复杂的例子中,可能包含许多的强引用造成了一个对象之间的循环,这就更难诊断了。

为了避免这个问题,惯例是捕获一个弱引用self,像这样:

- (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self;
    self.block = ^{
        [weakSelf doSomething];   // capture the weak reference
                                  // to avoid the reference cycle
    }
}


通过捕获self的弱指针引用,block不会包含一个强关系对于XYZBlockKeeper对象,如果对象在调用之前被销毁,weakSelf指针会简单的被设置为nil。

Blocks Can Simplify Enumeration

除了一些实现方法,许多Cocoa和Cocoa Touch接口使用block简化普通的任务,比如集合枚举器。例如,NSArray类,提供了三个基于block的方法:

- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;

This method takes a single argument, which is a block to be invoked once for each item in the array:

    NSArray *array = ...
    [array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"Object at index %lu is %@", idx, obj);
    }];

这个block含有三个参数,前两个指向了当前类和数组中的下标。第三个参数是一个指向Boolean变量的指针,你可以使用她停止枚举器,像这样:

    [array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        if (...) {
            *stop = YES;
        }
    }];

你也可以通过使用enumerateObjectsWithOptions:usingBlock:方法自定义枚举器。例如,指定NSEnumerationReverse选项,会倒序迭代集合中的元素。

如果枚举器block中的代码是加强的处理器---并且安全的并发执行---你可以使用 NSEnumerationConcurrent选项:

    [array enumerateObjectsWithOptions:NSEnumerationConcurrent
                            usingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        ...
    }];


这个标志标明枚举器block的调用肯能是在多个线程中,如果block的代码是特别的加强处理器的话,就提供了一个潜在可能的性能增强。注意,当使用这个选项的时候,枚举器的排序是没有被定义的。

The NSDictionary class also offers block-based methods, including:

    NSDictionary *dictionary = ...
    [dictionary enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) {
        NSLog(@"key: %@, value: %@", key, obj);
    }];

This makes it more convenient to enumerate each key-value pair than when using a traditional loop, for example.

Blocks Can Simplify Concurrent Tasks

一个block表示一个不同的工作单元,她是把执行代码和捕获周围范围内的状态结合到了一起。对于OS X和iOS来说,她使得异步调用变得很理想,当你使用并发选项的时候。与其必须解决如何使用底层的机制,比如线程,不如简单的定义你的任务使用block,然后让系统执行这些任务,因为处理器资源变得可用了。

OS X和iOS都为并发提供了许多的技术,包括两个任务调度机制:操作队列和中央调度。这些机制的想法围绕一个队列的任务等待调用。你按顺序添加你的块到队列中因为你需要他们被调用,当处理器时间和资源可用时调用系统出列。

OS X and iOS offer a variety of technologies for concurrency, including two task-scheduling mechanisms: Operation queues and Grand Central Dispatch. These mechanisms revolve around the idea of a queue of tasks waiting to be invoked. You add your blocks to a queue in the order you need them to be invoked, and the system dequeues them for invocation when processor time and resources become available.

一个连续的队列只允许一个时间段执行一个任务--直到前一个任务结束,下一个任务才执行。并发队列调用尽可能多的任务,而不必等待之前的任务完成。


Use Block Operations with Operation Queues


操作队列是Cocoa和Cocoa Touch调度方法。你创建一个NSOperation实例,封装一个工作单元以及任何所需的数据,然后添加NSOperationQueue执行该操作。

Although you can create your own custom NSOperation subclass to implement complex tasks, it’s also possible to use the NSBlockOperation to create an operation using a block, like this:

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    ...
}];

It’s possible to execute an operation manually but operations are usually added either to an existing operation queue or a queue you create yourself, ready for execution:

可能你手动执行一个操作,但是操作通常是添加到一个已经存在的操作队列中或者是你自己定义的队列中,准备执行:

// schedule task on main queue:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
 
// schedule task on background queue:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];

If you use an operation queue, you can configure priorities or dependencies between operations, such as specifying that one operation should not be executed until a group of other operations has completed. You can also monitor changes to the state of your operations through key-value observing, which makes it easy to update a progress indicator, for example, when a task completes.

如果你使用操作队列,您可以配置优先级或操作之间的依赖关系,比如指定一个操作不应执行,直到一群其他操作完成。你也可以改变你的状态进行监测,通过键值观察,这使得它容易更新进度,例如,当一个任务完成时

For more information on operations and operation queues, see Operation Queues.

Schedule Blocks on Dispatch Queues with Grand Central Dispatch

如果你需要安排任意的一个block代码块执行,你可以直接使用中央调度(GCD)控制的调度队列。调度队列使得任何对于调度者来说同步或者异步的执行任务变得很容易,执行任务是按先进先出的原则执行的。

你可以创建自己的调度队列或者使用GCD提供的队列。如果你需要安排一个任务为当前的实现,例如,你可以参考现有的队列使用dispatch_get_global_queue()函数并指定队列优先级,像这样:

You can either create your own dispatch queue or use one of the queues provided automatically by GCD. If you need to schedule a task for concurrent execution, for example, you can get a reference to an existing queue by using the dispatch_get_global_queue() function and specifying a queue priority, like this:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

To dispatch the block to the queue, you use either the dispatch_async() or dispatch_sync() functions. The dispatch_async() function returns immediately, without waiting for the block to be invoked:

为了让block进入队列,你可以使用dispatch_async()或者dispatch_sync()函数。dispatch_async()函数立即返回,没有等待块被调用:

dispatch_async(queue, ^{
    NSLog(@"Block for asynchronous execution");
});

The dispatch_sync() function doesn’t return until the block has completed execution; you might use it in a situation where a concurrent block needs to wait for another task to complete on the main thread before continuing, for example.

dispatch_sync()函数不返回,直到块执行完成;您可以使用它在一个情况下,并发块需要等待另一个任务完成主线程继续之前,例如

For more information on dispatch queues and GCD, see Dispatch Queues.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值