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 <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变量为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赋值会在编译时报错。

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 <Foundation/Foundation.h>

@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 <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;
}

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

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

定义Block类型

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

// 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

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

总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容