原文出处: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赋值会在编译时报错。
既然非局部变量是作为常量被拷贝进来的,也就是说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更加直观(只要熟悉了语法)。他们可以在行内定义,这使得他们使用非常简单,而且也可以很方便的捕捉到周围的非局部变量。