Blocks - 块

来源于 Ry’s Objective-C Tutorial - RyPress

一个学习Objective-C基础知识的网站.

个人觉得很棒,所以决定抽时间把章节翻译一下.

本人的英语水平有限,有让大家误解或者迷惑的地方还请指正.

原文地址:http://rypress.com/tutorials/objective-c/blocks.html

仅供学习,如有转摘,请注明出处.


块(下文直接用Block)是OC中的匿名函数.允许你在对象之间传递任意的语句,这种方式往往比查阅(引用)某处定义的函数更直观.此外,Block被实现成闭包,使它很容易捕获其周围(上下文)(其他变量)的状态.

创建Block

Block的使用跟普通函数完全一样.你可以声明一个block变量,(这个过程)跟你声明一个函数一样,然后定义这个Block,也跟实现函数一样,随后调用这个Block,还是跟调用函数一样:

// 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.跟函数声明类似,你也需要包含返回值类型,参数类型以便编译器能保证(强制)类型安全.与指针类型变量(比如,int *aPOinter)前的星号表现行为相似.^只在声明block时使用,随后你就可以跟使用其他普通变量一样使用block.

block本身其实是一个没有函数名的函数定义.上述的^double(double rate, double time)签名解释为:返回值为double类型,有两个double类型的参数(返回类型可以被忽略,如果你想的话).在花括号{}内可以写任意语句,这跟普通函数一样.

在把block赋值给distanceFromRateAndTime变量后,我们就可以把这个变量当做函数来调用.

无参数的Blocks

如果一个block不带任何参数,那么你可以忽略这个block中的参数列表.同样的,block中的返回值始终是可选的,所以你可以把(block)定义(采用的符号)缩短成^ {}:

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

内置的arc4random()函数返回一个随机的32位整型值.通过除以能返回的可能最大值(4294967295)来得到一个0到1之间的浮点值.

截止目前,block看起来就像一个更复杂的定义函数方式.而实际上是,它以闭包来实现,从而开启了令人兴奋的新编程机会的大门( But, the fact that they’re implemented as closures opens the door to exciting new programming opportunities).

闭包

在block内部,你有权访问数据,包括局部变量,传递给bloclk的参数,以及全局变量/函数.但由于blocks以闭包实现,也就意味着你也有权访问非局部变量.非局部变量是定义在block上下文中,而并不在block内部.比如,getFullCarName可以引用在block之前定义的make变量.

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

非局部变量实际上是被拷贝后与block一起当做常量存储的,这就意味着它们是只读的.如果尝试着在block内给make变量赋新值,就会抛出一个编译错误.

Accessing non-local variables as const copies
Accessing non-local variables as const copies

实际上,非局部变量被拷贝成常量意味着一个block不仅有权访问这些非局部变量-它(还)创建了这些非局部变量的快照(副本).非局部变量在block定义时,就把它们自身携带的值冻结到这个block中了(Non-local variables are frozen at whatever value they contain when the block is defined),并且这个block始终用的都是被冻结(拷贝)的值,即使这个非局部变量随后在程序中更改了.让我们来一下,在创建block之后改变make变量发生了什么事情.

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存储修饰符来声明一个变量以重写它的const复制行为.

__block NSString *make = @"Honda";

这种(声明方式)告诉block通过引用来捕获这个变量,即,在block内部使用的make变量与其之外的使用的make变量之间创建一个直接关系.现在,你就可以在block外部给make赋新值,它会反映在block中,反之亦然.

Accessing non-local variables by reference
Accessing non-local variables by reference

像普通函数中的静态局部变量一样,__block修饰符可以充当多次调用block时使用的记忆体.This makes it possible to use blocks as generators(这句太蛋疼了,理解为把block当做一个生成器使用).例如下面的代码段,它创建了一个可以在随后多次执行中记住i值的block.

__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当做变量存储并不常用,在实际情况下,blocks更多的被当做方法参数使用.它们被作为函数指针来解决同样的问题[此处不是很明白这个same question指代的是什么],但因为它们被定义成内联的,所以生成的代码更易阅读.

下面的例子中,Car接口声明了一个记录汽车行驶距离的方法.它不用强迫调用者必须传递一个速度常量,而是接收一个block,这个block利用时间来计算速度(the block defines the car’s speed as a function of time).

// 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(^)(double time),标识着无论调用者传递什么样的block,这个block的返回值必须是double类型,并且只接收单一的double类型参数.注意一点,此处的block跟该章节刚开始讨论的block变量声明基本是一样的语法,除了没有变量名.

实现文件中可以通过speedFunction来调用这个block.下面的例子使用单纯的黎曼加法来近似得到持续时间中汽车行驶的距离(The following example uses a naïve right-handed Riemann sum to approximate the distance traveled over duration).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可以在方法调用中定义.尽管这需要时间解析这种语法,但是这比创建一个专用的函数来定义withVariableSped参数更直观.

// 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多功能用处的其中一个例子,标准框架中充满了其他block的用例.NSArray允许使用带有block的sortedArrayUsingComparator:方法来对元素进行排序,而UIView通过animateWithDuration:animations:方法中的一个block来决定动画的最终状态.

另外,NSOpenPanel类可以在用户选中一个文件后执行一个block.这种便捷行为在[Ry’s Cocoa Tutorial]的[ Persistent Data]章节有探讨.

定义Block类型

因为block的数据类型语法会让你的方法变得混乱,所以typedef一个公用的block签名很有用.如下面的代码,创建了一个被称作SpeedFunction的新类型,以便我们能在语义上更容易理解withVariable参数.

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

很多OC中的标准框架都使用这个技术(比如:NSComparator)

总结

Block提供了很多与C函数相同的功能,但是他们使用起来更直观(在你习惯了它的语法后).由于他们可以被定义成内联的,所以很容易在方法调用中使用,而且它们是闭包的,所以它们不费吹飞之力就可获取周围(上下文)的变量值.

下个模块,我们安排一些有关iOS以及OS X的错误处理能力的内容.我们会探究两种用来表示错误的类:NSException和NSError.


写于15年09月11日 - 希望世界不再有恐怖袭击

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

推荐阅读更多精彩内容