Objc中的Blocks

原文出处:Ry's Objective-C Tutorial,侵删。

Blocks

Blocks是Objc中的匿名函数。Blocks允许你在对象之间传递任意的statements,通常比使用函数要直观。此外,blocks是使用closures实现的,这使得它可以更容易的捕捉周围的状态。

Declare & Implement/Define

Blocks使用与函数类似的机制。你可以使用declare函数类似的方式declare一个block变量,define一个block就像在implement一个函数一样,而且调用方式也与函数类似。

// main.m
#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Declare the block variable
        double (^distanceFromRateAndTime)(double rate, double time);
        
        // Create and assign the block
        distanceFromRateAndTime = ^double(double rate, double time) {
            return rate * time;
        };
        // Call the block
        double dx = distanceFromRateAndTime(35, 1.5);
        
        NSLog(@"A car driving 35 mph will travel "
              @"%.2f miles in 1.5 hours.", dx);
    }
    return 0;
}

符号^用来标记distanceFromRateAndTime变量为block。就像函数的声明一样,你也需要告诉编译器block的返回值型、参数类型,编译器才可以保证类型安全。符号^就像指针声明中的*符号一样,它只是用来声明一个block,之后你就可以像一个正常变量一样使用它。

Block本质上就是一个函数定义,只不过函数没有名字。^double(double rate, double time)签名建立了一个block,作用是返回一个double类型的变量,同时接受两个double类型参数(不一定要有返回值)。任意的statements都可以放到{}当中。

在将block的内容指定给distanceFromRateAndTime之后,就可以像调用函数一样调用这个变量了。

无参数的blocks

如果block没有任何参数,你就可以在方法的entirety中忽略参数列表。结合前面提到的返回值也是可选的,其实我们就可以声明block:^{...}

double (^randomPercent)(void) = ^ {
    return (double)arc4random() / 4294967295;
};
NSLog(@"Gas tank is %.1f%% full",
      randomPercent() * 100);

这个内建函数arc4random()返回一个32位的任意整数。再除以最大的32位整数,就可以得到一个0到1之间的数字。

目前为止,你好像只是觉得block就是函数的一种更加复杂的定义方式。Block到底有何用呢?事实上,blocks是使用closures实现的,这就为我们提供了全新的编程方法。

Closures (闭包)

在block内部,你访问数据的权限与正常的函数一样:局部变量、传递进来的参数变量、全局变量/函数。但是,blocks是使用closures实现的,也就是说,你也可以访问非局部变量。非局部变量指的是那些定义在block包括的词法作用域之内,但是在block之外的变量。举个例子,getFulCarName可以访问在block之前就定义好的make变量(注意此时make*并没有作为block的参数被传入block当中)。

NSString *make = @"Honda";
NSString *(^getFullCarName)(NSString *) = ^(NSString *model) {
    return [make stringByAppendingFormat:@" %@", model];
};
NSLog(@"%@", getFullCarName(@"Accord"));    // Honda Accord

非局部变量通过拷贝的方式,作为常量被保存到Block当中,也就是说在block中他们是只读的。如果试图给make赋值会在编译时报错。

Objc中的Blocks_第1张图片
const-non-local-variables.png

既然非局部变量是作为常量被拷贝进来的,也就是说block事实上创建了非局部变量的快照(snapshot)。非局部变量的值在编译阶段、block定义的时候就被冻结了,block只能使用快照中的值,计时非局部变量在之后block之后改变过值。下面的例子帮助理解:

NSString *make = @"Honda";
NSString *(^getFullCarName)(NSString *) = ^(NSString *model) {
    return [make stringByAppendingFormat:@" %@", model];
};
NSLog(@"%@", getFullCarName(@"Accord"));    // Honda Accord

// Try changing the non-local variable (it won't change the block)
make = @"Porsche";
NSLog(@"%@", getFullCarName(@"911 Turbo")); // Honda 911 Turbo

闭包可以很方便的与上文互动,因为它减少了传递额外参数的操作——你可以在block中直接调用已经定义好的非局部变量。

可变的非局部变量

非局部变量作为常量传入是一种安全的、默认的方式,因为这样可以避免你不小心在block中改变非局部变量的值;但是,有些时候你如果想改变他们。你可以再定义非局部变量的时候使用修饰符__block

__block NSString *make = @"Honda";

这告诉block直接捕捉非局部变量的引用。现在,你就可以在block中改变make的值了,而且,在block之后改变make的值,也会影响block中的数值。
就像通常函数中的静态局部变量一样,__block可以使block作为generator。下面的代码片段演示了如何使用block来记录i的值。

__block int i = 0;
int (^count)(void) = ^ {
    i += 1;
    return i;
};
NSLog(@"%d", count());    // 1
NSLog(@"%d", count());    // 2
NSLog(@"%d", count());    // 3

Block作为函数参数

Block作为函数参数是更加常用的方法。它和函数指针类似,但是block可以用inline的方式定义,所以代码可读性更好。

举例来说,下面的代码段Car接口,声明了一个函数计算车的行驶距离。他通过一个block来返回车辆的速度,而Block又接受一个时间参数来改变速度。

// Car.h
#import 

@interface Car : NSObject

@property double odometer;

- (void)driveForDuration:(double)duration
       withVariableSpeed:(double (^)(double time))speedFunction
                   steps:(int)numSteps;

@end

block的数据类型是(^)(double time),也就是说任何传入这个方法的block都必须接受一个double类型的参数并返回一个double类型的参数。注意到这个声明方式和本文开头的基本一样,区别在于该block是匿名的。

实现可以通过调用speedFunction来调用block。下面的例子使用了简单的黎曼和来估计距离。steps是用来定义精度的参数。

// Car.m
#import "Car.h"

@implementation Car

@synthesize odometer = _odometer;

- (void)driveForDuration:(double)duration
       withVariableSpeed:(double (^)(double time))speedFunction
                   steps:(int)numSteps {
    double dt = duration / numSteps;
    for (int i=1; i<=numSteps; i++) {
        _odometer += speedFunction(i*dt) * dt;
    }
}

@end

在下面的main函数中可以看到,block的含义(literals)可以在函数的实际调用时定义。虽然需要一些时间来解析这个语法(syntax),但是这样仍然读起来很直观(这里指的是与传入函数指针相比,因为函数指针的话需要去函数定义的地方才能明白到底做了什么)。

// main.m
#import 
#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *theCar = [[Car alloc] init];
        
        // Drive for awhile with constant speed of 5.0 m/s
        [theCar driveForDuration:10.0
               withVariableSpeed:^(double time) {
                           return 5.0;
                       } steps:100];
        NSLog(@"The car has now driven %.2f meters", theCar.odometer);
        
        // Start accelerating at a rate of 1.0 m/s^2
        [theCar driveForDuration:10.0
               withVariableSpeed:^(double time) {
                           return time + 5.0;
                       } steps:100];
        NSLog(@"The car has now driven %.2f meters", theCar.odometer);
    }
    return 0;
}

这是一个block应用的简单例子,但是标准库里面有很多使用的地方。比如,NSArray的sortedArrayUsingComparator:方法使用block来排序元素;UIView的*animateWithDuration:animations: *方法使用block来定义动画结束后最终的状态。

此外,NSOpenPanel通过block来执行用户选择文件后的操作。

定义Block类型

Block的语法比较冗长,所以,我们通常通过typedef来定义一些常用的block。比如,下面的代码段创建了一个新的类型SpeedFunction,我们可以使用一个语义更加明确(more semantic)的参数:

// Car.h
#import 

// Define a new type for the block
typedef double (^SpeedFunction)(double);

@interface Car : NSObject

@property double odometer;

- (void)driveForDuration:(double)duration
       withVariableSpeed:(SpeedFunction)speedFunction
                   steps:(int)numSteps;

@end

许多标准库都使用了这样的方式,比如NSComparator。

总结

Blocks是函数的补充,Blocks更加直观(只要熟悉了语法)。他们可以在行内定义,这使得他们使用非常简单,而且也可以很方便的捕捉到周围的非局部变量。

你可能感兴趣的:(Objc中的Blocks)