首页 > IOS > 区块--Objective-C编程实践

区块--Objective-C编程实践

来源:原创 作者:thomas 分类:IOS 阅读:742 日期:2015-03-23

Objective-C类可以定义一个包含数据及相关行为的对象。有时,仅需要定义一个单任务或一个行为,而不是一系列方法集合。

block被作为一个语言级特性添加到C、Objective-C和C++中,允许你创建不同的代码片段,并可以作为值传递给方法或函数。区块作为Objective-C对象,可以被添加到像NSArray或NSDictionary这样的集合中。它们有从封闭作用域内获取值的能力,类似于其他语言中的闭包或lambdas。

本章描述了声明和引用区块的语法,以及在常用任务中如何使用区块,例如集合遍历。

区块语法

定义区块的字面量语法为使用标号^,例如:

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

类似于方法和函数的定义,大括号标记着区块的开始与结束。上个示例中,区块不返还任何值,也不接收任何参数。

类似于你可以使用函数指针来引用C函数,你也可以声明一个变量来保存区块,如下:

void (^simpleBlock)(void);

如果你不常使用C函数指针,这个语法可能看起来有点奇怪。这个示例声明一个叫simpleBlock的变量来引用一个不接收任何参数也不返还任何值的区块,这意味着该变量可被直接赋予区块字面量声明,如下:

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

类似于其他的变量声明,该语句后面必须以分号结尾。你也可以把变量声明和赋值组合到一起:

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

一旦你声明并初始化了一个区块,你可以按如下方式调用它:

simpleBlock();

注意:如果你尝试调用一个未赋值的区块变量(nil区块变量),你的程序将会崩溃。

有参数和返回值的区块

区块也可以像方法和函数一样拥有参数和返回值。

示例如下:

double (^multiplyTwoValues)(double, double);

对应的区块定义字面量形式如下:

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

当区块被调用时,firstValue和secondValue被用来指向引用的值,类似于函数定义。这个例子中,返回值类型由区块中的return语句来决定。

你也可以明确地把返回值类型放置到^和变量列表之间:

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

一旦你声明并定义了区块,你可以调用它,就像调用函数一样:

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

区块可以从封闭作用域中获取值

就像包含可执行的代码一样,区块拥有从封闭作用域获取变量的能力。

如果你在一个方法中定义区块,则可以从方法作用域中获取任何可访问的值,例如:

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

在这个例子中,anInteger在区块外声明,但它的值可以在区块定义中被捕获。

仅值被捕获,而不管你在其他地方如何修改。意思是你在定义区块和调用区块之间更改外部变量的值,并无任何影响:

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

区块获取的值并无任何影响。输出结果如下:

Integer is: 42

同样也表示区块不能改变原变量的值,也既是捕获的值(捕获的值作为常量)。

使用__block修饰变量来共享存储空间

如果你想在区块中改变捕获的变量的值,你可以在原变量声明时使用__block存储类型修饰符。这意味着变量存储在原变量声明的作用域及任意被定义在该作用域中的区块之间的共享存储空间中。

例如,你可以如下重新先前的例子:

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

由于anInteger被声明为__block变量,它的存储空间是和区块声明的空间共享的。输出结果如下:

Integer is: 84

这也意味着区块可以修改原变量的值:

__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

传递区块作为方法和函数的参数

本章中先前的每个例子都是在区块定义后立即调用区块。实际编程中,通常传递区块到方法和函数中来调用。你可能使用GCD在后台调用区块,定义一个被重复调用的区块,例如在枚举一个集合时。并发及枚举将在其他章节介绍。

区块通常被用作回调,当一个任务完成时,定义一段可被执行的代码。举例来说,你的APP可能需要相应一个用户动作在执行完一个复杂的任务时,例如从web service请求信息。由于任务可能需要花费很长时间,当任务发生时,你需要展示一些进度指示器,而当任务完成时隐藏掉。

也可以通过使用代理完成这项任务:你需要创建一个合适的代理协议,实现必要的方法,设置你的对象作为任务的代理类,一旦任务完成时,将在你的对象上调用设置好的代理方法。

区块把这种开发任务变得更加容易,因为在你启动任务的时候,你可以像如下方式定义回调方法:

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

这个例子调用一个方法来展示进度指示器,接着创建任务并启动。一旦任务完成,回调区块指定的代码将被执行;这个例子中,区块仅简单地调用一个方法以隐藏进度指示器。需要注意的是,这个回调区块捕获self变量以便于在执行时能够调用hideProgressIndicator方法。需要留意的是,在捕获self变量时,极有可能创建强索引循环,下面章节将描述在捕获self变量时,如何避免强索引循环。

从代码可读性方面来说,区块使得在同一个代码片段处直接看到在任务完成前后都发生了什么变得更容易,避免了需要跟踪进入代理方法以查找发生了什么。

上例中的beginTaskWithCallbackBlock方法的声明为:

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

类型(void (^)(void))指明了参数是一个不接收任何参数,也不返回任何值的区块。方法实现中以常用的方式调用区块:

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

方法参数中的区块也可以指定一个或多个参数,如下:

- (void)doSomethingWithBlock:(void (^)(double, double))block {
    ...
    block(21.0, 2.0);
}

区块应该被设置为方法的最后一个参数

最佳编程实践中,通常使用区块作为方法的参数。如果方法需要其他的非区块参数,区块总被设置为最后一个参数:

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

在区块内容指明逻辑,这将使得方法调用变得更容易阅读。

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

 

热门文章 更多>

微信扫一扫,关注技术十日谈