原文:http://rypress.com/tutorials/objective-c/blocks
Blocks 块
块是OC的匿名函数。块特性使得能够在不同类之前传递某段代码(函数),这比去调用某个地方定义的某个方法更加直观,另外由于块是封闭的,所以可以排除外部干扰。
创建块
块是基于函数的。你可以申明一个块就像声明一个函数一样,实现一个块和实现一个函数基本上没有区别。
// main.m #import <Foundation/Foundation.h> 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为一个块变量。就像声明函数一样,你需要指明块的返回值和参数类型等。(^
)符号符号可以很形象的和指针符号做类比(比如:int *aPointer),声明块变量后向指针变量一样去使用。
块定义自身本质上就是函数的定义-只是没有函数名而已。^double(double rate, double time)标示当前块返回类型是double类型,有两个double类型的参数,然后在花括号中定义自己的处理逻辑就像普通的函数那样。
将定义赋给变量后,我们使用这个快变量如同使用普通函数那样。
无参数块
如果一个块没有任何参数,你可以在块声明中省略参数部分内容。另外块的返回值在定义的时候是可选的(可写可不写),具体如下:
double (^randomPercent)(void) = ^ { return (double)arc4random() / 4294967295; }; NSLog(@"Gas tank is %.1f%% full", randomPercent() * 100);
当目前为止似乎块就是使用另外一种难懂的方式去定义方法。事实上它开启了全新的变成方式的大门。
封闭
在块中,访问数据和在方法中没有区别比如:本地变量,参数和全局变量全局函数。但是块是封闭的,所以他能访问非本地变量。非本变量是指定义在块外部的变量,例如下面的getFullCarName可以读取make变量的值。
NSString *make = @"Honda"; NSString *(^getFullCarName)(NSString *) = ^(NSString *model) { return [make stringByAppendingFormat:@" %@", model]; }; NSLog(@"%@", getFullCarName(@"Accord")); // Honda Accord
非本地变量在块内部使用的时候其实是原变量的一个拷贝而已,换句话说你在块中是无法修改原变量的值的他们是只读的。如果强行在块中修改非本地变量的值将会出现编译错误。
Accessing non-local variables as const
copies
事实上在块中事实访问到了非本地变量的一个快照,无论外部非本地变量如何被修改,在块内部这个快照的值依然保持块定义的时候非本地变量的值,不会随着非本地变量的变化而变化,如下:
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 NSString *make = @"Honda";
这样声明后就告诉块这个变量被直接链接了,可以修改它的值。
Accessing non-local variables by reference
就像static
local variables静态本地变量在函数中的功能一样,__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
块作为参数
块变量或许有用,但是在现实中块经常被用于作为参数传递。他们好比函数指针但是他们可以一种内联的方式直接定义这样更加易读易懂。
例如,下面的Car接口声明了一个行驶距离的方法。它的一个参数采用块的方式。
// Car.h #import <Foundation/Foundation.h> @interface Car : NSObject @property double odometer; - (void)driveForDuration:(double)duration withVariableSpeed:(double (^)(double time))speedFunction steps:(int)numSteps; @end
块的数据类型为 double (^)(double time),指明传入的块必须返回类型为double并且具有有一个double的参数。注意块参数类型的写法和声明一个参数很像只是没有块变量名而已。
实现这个方法的时候可以直接使用块参数名speedFunction来调用块。下面的例子:
// 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方法中看到的,块可以在方法被调用的时候直接定义:
// main.m #import <Foundation/Foundation.h> #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; }
上述举了一个简单地块的例子,但是在标准的框架中确存在很多块的东西。 NSArray可以让你通过sortedArrayUsingComparator:使用块来对元素进行排序。
UIView的
animateWithDuration:animations:方法
使用块定义最终的animation的状态。
定义块类型
语法上块的类型比较杂乱,我们可以使用typedef来为块类型做签名,例如,下面的代码中新建了一个新的块类型叫做SpeedFunction,这样我们可以使用
SpeedFunction来声明对应的块变量了。
// Car.h #import <Foundation/Foundation.h> // 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
总结
块提供了很多的C的函数的功能,但是相比较与函数块更加直观和灵活。
下一个章节中我们将进入IOS和OS X的错误处理环节。我们将介绍两个重要的错误类:NSException和NSError;