Block的使用(Working with Blocks)

本文介绍了Objective-C中的Block概念,包括其定义、语法及如何使用Block简化常见任务,如枚举集合和并发处理。
一个Objective-C定义了一个对象,这个对象可以将数据与相关行为结合。有时,它仅仅代表一个任务或行为单元,而不是方法的集合。
Block是添加到C、 Objective-C 和C++的语言级特性,允许创建不同的代码段传递到方法或函数中,就像传值一样传递。Block是Objective-C对象,可以添加到类似NSArray 或NSDictionary的集合中。它们可以获取作用域的值,使之类似于其他编程语言的闭包或lambdas 。
本章解释了声明和引用block的语法,并展示了如何使用block来简化常见任务例如枚举集合。关于更多信息,可查看block编程主题(Blocks Programming Topics)。

Block语法

定义block的语法使用插入符号(^)
^{
         NSLog(@"This is a block");
    }
与函数和方法定义一样,花括号表示block的开始和结束。在这个例子中,block不会返回任何值并且不带任何参数。
可以使用一个函数指针来引用C函数,以同样的方式,可以声明一个变量来跟踪一个block,如下:
    void (^simpleBlock)(void);
如果不习惯处理C函数指针,block语法似乎有些不同。这个例子声明了一个名叫simpleBlock 的变量引用block,这个block不带参数也没有返回值,这表明这个变量可以被上边的block执行,如下:
    simpleBlock = ^{
        NSLog(@"This is a block");
    };
和其他变量的赋值一样,声明必须用括号分号终止。可以结合变量声明与赋值:
    void (^simpleBlock)(void) = ^{
        NSLog(@"This is a block");
    };
一旦block变量声明并赋值,可以用它来调用block:
    simpleBlock();
注:如果试图调用block,且该block中使用未赋值的变量(block变量为nil),app则会崩溃。

Block参数与返回值

Block也可以像方法和函数一样带参数和返回值。
举个例子,用一个变量来引用block,该block返回两个值相乘的结果:
    double (^multiplyTwoValues)(double, double);
相应的block如下:
    ^ (double firstValue, double secondValue) {
        return firstValue * secondValue;
    }
就像其他函数的定义,在block被调用时,firstValue 和secondValue 用来提供值。在这个例子中,返回值的类型是从block内部的返回语句推断出来的。
可以通过在脱字符和参数列表间显示的指定返回类型:
    ^ double (double firstValue, double secondValue) {
        return firstValue * secondValue;
    }
一旦声明和定义了一个block,可以像函数一样调用它:
    double (^multiplyTwoValues)(double, double) =
                              ^(double firstValue, double secondValue) {
                                  return firstValue * secondValue;
                              };
 
    double result = multiplyTwoValues(2,4);
 
    NSLog(@"The result is %f", result);

Block可以捕获作用域的值

Block包含可执行代码,也可以获取作用域的状态。
举个例子,如果在一个方法中声明了一个block,那么可以捕获该方法作用域内的任何值,如下:
- (void)testMethod {
    int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    testBlock();
}
在这个例子中,在block外声明了一个interger变量,但当block定义时,可以获得该值。
只捕获值,除非另外指定。如果你在定义block和调用block之间改变外部变量值,如下:
    int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    anInteger = 84;
 
    testBlock();
Block捕获的值不受影响。Log输出显示为:
Integer is: 42
这表明block不改变原始变量的值,甚至是捕获的值(这是作为一个常量来捕获)。

使用__block变量共享存储

如果需要改变block中捕获的变量值,可以在原始变量声明时使用__block来存储类型修饰符。这表明在原始变量作用范围内,变量存储的区域与该范围定义的block是共享的。
举个例子,可能重写前面的例子,如下:
    __block int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
    };
 
    anInteger = 84;
 
    testBlock();
因为interger被声明为__block变量,存储区域与block声明是共享的。这表明log将输出:
Integer is: 84
这表明block可以修改原始值,如下:
    __block int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
        anInteger = 100;
    };
 
    testBlock();
    NSLog(@"Value of original variable is now: %i", anInteger);
这次将输出:
Integer is: 42
Value of original variable is now: 100

可以将block作为参数传递给方法或函数

在本章中前面的每个例子都是在block定义后立即调用block。在实际中,通常传递block到其他地方调用的函数或方法中。可以在后台使用GCD调用block,例如,定义一个block来代表一个反复调用的任务,例如枚举一个集合的时候。在本章的后面将讨论并发性和枚举。
Block也用于回调函数,在任务完成时执行定义的代码。举个例子,应用程序可能需要通过创建一个可执行复杂任务的对象来响应用户操作,例如向web服务器请求信息。因为这个任务需要很长时间,所以要求当任务发生时显示进度,当任务完成时隐藏。
使用委托可以做到:需要创建一个合适的委托协议来实现所需的方法,并设置对象的委托任务,然后等待调用,一旦任务完成立即调用对象的委托方法。
Block使之变得更容易,因为可以在启动任务时定义回调行为,如下:
- (IBAction)fetchRemoteInformation:(id)sender {
    [self showProgressIndicator];
 
    XYZWebTask *task = ...
 
    [task beginTaskWithCallbackBlock:^{
        [self hideProgressIndicator];
    }];
}
这个例子调用一个方法来显示进度指示器,然后创建任务并告知任务开始。回调block指定任务完成时需要执行的代码,在这种情况下,它调用一个方法来隐藏进度指示器。注意,这个回调block捕获self是为了调用hideProgressIndicator方法。需要重点注意的是,当捕获self时很容易创建一个strong应用循环,如之后在Avoid Strong Reference Cycles when Capturing self中描述的。
从代码的可读性来看,block使我们可以在一个地方看到任务完成之前和之后将发生什么,避免通过跟踪委托方法来查看会发生什么
例子中beginTaskWithCallbackBlock: 方法的声明如下:
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock;
(void (^)(void)) 表明是一个不带任何参数或返回值的block。该方法的实现可以以常用的方式调用block:
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
    ...
    callbackBlock();
}
带有一个或多个参数的block,作为方法的参数与block变量的声明方法一致。
- (void)doSomethingWithBlock:(void (^)(double, double))block {
    ...
    block(21.0, 2.0);
}

Block必须为方法的最后一个参数

最好一个方法只有一个block参数。如果方法还需要其他非block参数,则block必须在最后:
- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback;
指定block内联,这样使方法调用更容易阅读,如下:
    [self beginTaskWithName:@"MyTask" completion:^{
        NSLog(@"The task is complete");
    }];

使用类型定义简化block语法

如果需要定义多个相同签名的block,可以自定义签名类型。
举个例子,可以为不带参数和返回值的block定义一个类型,如下:
typedef void (^XYZSimpleBlock)(void);
方法参数或者创建block变量时,可以使用自定义类型:
    XYZSimpleBlock anotherBlock = ^{
        ...
    };


- (void)beginFetchWithCallbackBlock:(XYZSimpleBlock)callbackBlock {
    ...
    callbackBlock();
}
当处理返回block或带有其他block参数的block时,自定义类型非常有用。考虑下面的例子:
void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
    ...
    return ^{
        ...
    };
};
变量引用一个block,这个block有一个block(aBlock)参数并且返回另一个block。
使用类型定义重写代码使得代码更易读:
XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) {
    ...
    return ^{
        ...
    };
};

对象使用属性来跟踪block

定义一个属性跟踪block的语法类似于定义block变量的语法:
@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end
注:必须指定copy作为property的属性,因为一个block在原始范围之外需要复制来跟踪捕获状态。当使用ARC时,不用担心,因为它会自动发生,但是最好用property的属性来显示由此导致的行为。更多信息,参见block编程主题。
像其他block变量一样设置或调用block属性:
    self.blockProperty = ^{
        ...
    };
    self.blockProperty();
Block属性声明也可以使用类型定义,如下:
typedef void (^XYZSimpleBlock)(void);
 
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end

捕获self时避免强引用循环

如果需要在block中捕获self,例如定义一个回调block,重点要考虑内存管理的影响。
对于任何捕获的对象,包括self,block保持为强引用,这表明很容易以强引用而结束。例如,一个对象保持copy属性,改属性为捕获self的block:
@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 对象保持强关系。如果在block调用前,这个对象被释放,weakSelf 指针将设置为nil。

Block可以简化枚举

除了一般完成处理程序,许多Cocoa 和Cocoa Touch API 使用block来简化常见的任务,例如集合枚举。例如,NSArray 类提供了三种基于block的方法:
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
这个方法需要一个参数,这个参数是一个block,在数组中的每项都要调用一次:
    NSArray *array = ...
    [array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"Object at index %lu is %@", idx, obj);
    }];
Block本身带三个参数,前面两个是指向当前对象和数组的索引。第三个参数是一个指向bool型变量的指针,可以用来停止枚举,如下:
    [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代码是密集处理型,它可以提供潜在的性能提升。注意,当使用这个选项时,枚举的顺序是未定义的。
类还提供了基于block的方法,包括:
    NSDictionary *dictionary = ...
    [dictionary enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) {
        NSLog(@"key: %@, value: %@", key, obj);
    }];
相比使用传统的循环,这个方法使枚举每个键值对更加方便。

Block可以简化并发任务

一个block代表不同的工作单元,可将可执行代码与作用域范围内捕获的可选状态相结合。在OS X和iOS中,异步调用可使用并发选项。而不必理解如何使用底层机制,如线程,可以简单的使用block定义任务,然后让系统在处理器资源可用时执行这些任务。
OS X 和iOS 为并发提供各种技术,包括两种任务调度机制:操作队列和GCD。这些机制围绕着任务队列等待调用。可以添加block到队列按照需要的顺序被调用,当处理器时间和资源可用时,block将出队,系统调用block。
串行队列一次只允许执行一个任务,直到上一个任务完成,下一个任务才会出队并被调用。而并发队列尽可能调用更多的任务,而不用等待完成前面的任务。

使用block操作操作队列

操作队列是Cocoa 和Cocoa Touch任务调度的一种方法。可以创建一个NSOperation 实例封装一个所需的数据到工作单元,然后添加到NSOperationQueue 执行该操作
虽然可以创建自定义NSOperation 子类实现复杂的任务,也可以使用NSBlockOperation 创建一个block操作,如下:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    ...
}];
可以手动的执行一个操作,但通常添加一个操作到现有的操作队列或者自定义队列,来准备执行:
// 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];
如果使用操作队列,可以配置优先级或者操作间的依赖关系,例如,可以指定一个操作不执行知道其他操作完成。还可以通过观察键值对监控操作状态的变化。当一个任务完成,可以很方便的更新进度指标。
了解操作和操作队列更多信息,可查看并发编程指南中的操作队列

关于GCD调度队列的block模块

如果需要安排执行任意block代码,可以直接使用GCD控制的调度队列。调度队列可以方便调用者同步或异步执行任务,这些任务是按照先进先出的顺序执行。
可以创建自定义调度队列或使用GCD提供的队列。如果需要安排一个任务并发执行,可以参考现有队列,使用dispatch_get_global_queue() 函数并指定队列优先级,如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
为了调度block到队列,可以使用dispatch_async() 或dispatch_sync()函数。dispatch_async() 函数会立即返回,而不必等待block被调用。
dispatch_async(queue, ^{
    NSLog(@"Block for asynchronous execution");
});
直到block执行完成,dispatch_sync()函数才返回,当一个并发block需要等待主线程的另一个任务完成后才继续时,可以使用它。
了解调度队列和GCD,可查看并发编程指南中的调度队列。




官方原文:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html#//apple_ref/doc/uid/TP40011210-CH8-SW1
/** * ap_scan - AP scanning/selection * * By default, wpa_supplicant requests driver to perform AP * scanning and then uses the scan results to select a * suitable AP. Another alternative is to allow the driver to * take care of AP scanning and selection and use * wpa_supplicant just to process EAPOL frames based on IEEE * 802.11 association information from the driver. * * 1: wpa_supplicant initiates scanning and AP selection (default). * * 0: Driver takes care of scanning, AP selection, and IEEE 802.11 * association parameters (e.g., WPA IE generation); this mode can * also be used with non-WPA drivers when using IEEE 802.1X mode; * do not try to associate with APs (i.e., external program needs * to control association). This mode must also be used when using * wired Ethernet drivers. * * 2: like 0, but associate with APs using security policy and SSID * (but not BSSID); this can be used, e.g., with ndiswrapper and NDIS * drivers to enable operation with hidden SSIDs and optimized roaming; * in this mode, the network blocks in the configuration are tried * one by one until the driver reports successful association; each * network block should have explicit security policy (i.e., only one * option in the lists) for key_mgmt, pairwise, group, proto variables. * * Note: ap_scan=2 should not be used with the nl80211 driver interface * (the current Linux interface). ap_scan=1 is optimized work working * with nl80211. For finding networks using hidden SSID, scan_ssid=1 in * the network block can be used with nl80211.
09-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值