我们可以把Block当做Objective-C的匿名函数。Block允许开发者在两个对象之间将任意的语句当做数据进行传递,往往这要比引用定义在别处的函数直观。另外,block的实现具有封闭性(closure),而又能

“”

本文转自破船的博客:

小引

本周末微博上朋友发了一个关于block的MV,只能说老外太逗了。大家也可以去看看怎么回事:Cocoa
Got Blocks
。虽然之前也有接触过block,不过没有深入完整的学习过,借此机会来学习一下,顺便翻译几篇block相关的文章,本文是第一篇,算是block的入门。本文的最后延伸阅读给出了4篇相关文章,不出意外的话,本周大家能看到对应的中文版。
 
目录
Block简介
Block的创建
不带参数的Block
Block的闭包性(closure)
修改非局部变量
Block作为函数的参数
定义Block类型
总结
延伸阅读
 
正文
Block简介
我们可以把Block当做Objective-C的匿名函数。Block允许开发者在两个对象之间将任意的语句当做数据进行传递,往往这要比引用定义在别处的函数直观。另外,block的实现具有封闭性(closure),而又能够很容易获取上下文的相关状态信息。
 
Block的创建
实际上,block使用了与函数相同的机制:可以像声明函数一样,来声明一个bock变量;可以利用定义一个函数的方法来定义一个block;也可以将block当做一个函数来调用。
  1. // main.m
  2. #import <Foundation/Foundation.h>
  3. int main(int argc, const char * argv[]) {
  4. @autoreleasepool {
  5. // Declare the block variable
  6. double (^distanceFromRateAndTime)(double rate, double time);
  7. // Create and assign the block
  8. distanceFromRateAndTime = ^double(double rate, double time) {
  9. return rate * time;
  10. };
  11. // Call the block
  12. double dx = distanceFromRateAndTime(35, 1.5);
  13. NSLog(@"A car driving 35 mph will travel "
  14. @"%.2f miles in 1.5 hours.", dx);
  15. }
  16. return 0;
  17. }
 在上面的代码中,利用插入符(^)将distanceFromRateAndTime变量标记为一个block。就像声明函数一样,需要包含返回值的类型,以及参数的类型,这样编译器才能安全的进行强制类型转换。插入符(^)跟指针(例如 int *aPointer)前面的星号(*)类似——只是在声明的时候需要使用,之后用法跟普通的变量一样。
 
block的定义本质上跟函数一样——只不过不需要函数名。block以签名字符串开始:^double(double rate, double time)标示返回一个double,以及接收两个同样为double的参数(如果不需要返回值,可以忽略掉)。在签名后面是一个大括弧({}),在这个括弧里面可以编写任意的语句代码,这跟普通的函数一样。
 
当把block赋值给distanceFromRateAndTime后,我们就可以像调用函数一样调用这个变量了。
 
不带参数的Block
如果block不需要任何的参数,那么可以忽略掉参数列表。另外,在定义block的时候,返回值的类型也是可选的,所以这样情况下,block可以简写为^ { … }:
  1. double (^randomPercent)(void) = ^ {
  2. return (double)arc4random() / 4294967295;
  3. };
  4. NSLog(@"Gas tank is %.1f%% full",
  5. randomPercent() * 100);
 
在上面的代码中,利用内置的arc4random()方法返回一个32位的整型随机数——为了获得0-1之间的一个值,通过除以arc4random()方法能够获取到的最大值(4294967295)。
 
到现在为止,block看起来可能有点像利用一种复杂的方式来定义一个方法。事实上,block是被设计为闭包的(closure)——这就提供了一种新的、令人兴奋的编程方式。
 
Block的闭包性(closure)
在block内部,可以像普通函数一样访问数据:局部变量、传递给block的参数,全局变量/函数。并且由于block具有闭包性,所以还能访问非局部变量(non-local variable)。非局部变量定义在block之外,但是在block内部有它的作用域。例如,getFullCarName可以使用定义在block前面的make变量:
  1. NSString *make = @"Honda";
  2. NSString *(^getFullCarName)(NSString *) = ^(NSString *model) {
  3. return [make stringByAppendingFormat:@" %@", model];
  4. };
  5. NSLog(@"%@", getFullCarName(@"Accord"));    // Honda Accord
 
非局部变量会以const变量被拷贝并存储到block中,也就是说block对其是只读的。如果尝试在block内部给make变量赋值,会抛出编译器错误。
以const拷贝的方式访问非局部变量,意味着block实际上并不是真正的访问了非局部变量——只不过在block中创建了非局部变量的一个快照。当定义block时,无论非局部变量的值是什么,都将被冻结,并且block会一直使用这个值,即使在之后的代码中修改了非局部变量的值。下面通过代码来看看,在创建好block之后,修改make变量的值,会发生什么:
  1. NSString *make = @"Honda";
  2. NSString *(^getFullCarName)(NSString *) = ^(NSString *model) {
  3. return [make stringByAppendingFormat:@" %@", model];
  4. };
  5. NSLog(@"%@", getFullCarName(@"Accord"));    // Honda Accord
  6. // Try changing the non-local variable (it won't change the block)
  7. make = @"Porsche";
  8. NSLog(@"%@", getFullCarName(@"911 Turbo")); // Honda 911 Turbo
 
block的闭包性为block与上下文交互的时候带来极大的便利性,当block需要额外的数据时,可以避免使用参数——只需要简单的使用非局部变量即可。
 
修改非局部变量
冻结中的非局部变量是一个常量值,这也是一种默认的安全行为——因为这可以防止在block中的代码对非局部变量做了意外的修改。那么如果我们希望在block中对非局部变量值进行修改要如何做呢——用__block存储修饰符(storage modifier)来声明非局部变量:
  1. __block NSString *make = @"Honda";
这将告诉block对非局部变量做引用处理,在block外部make变量和内部的make变量创建一个直接的链接(direct link)。现在就可以在block外部修改make,然后反应到block内部,反过来,也是一样。

 
通过引用的方式访问非局部变量
这跟普通函数中的静态局部变量(static
local variable)
类似,用__block修饰符声明的变量可以记录着block多次调用的结果。例如下面的代码创建了一个block,在block中对i进行累加。
  1. __block int i = 0;
  2. int (^count)(void) = ^ {
  3. i += 1;
  4. return i;
  5. };
  6. NSLog(@"%d", count());    // 1
  7. NSLog(@"%d", count());    // 2
  8. NSLog(@"%d", count());    // 3
 
Block作为函数的参数
把block存储在变量中有时候非常有用,比如将其用作函数的参数。这可以解决类似函数指针能解决的问题,不过我们也可以定义内联的block,这样代码更加易读。
 
例如下面Car interface中声明了一个方法,该方法用来计算汽车的里程数。这里并没有强制要求调用者给该方法传递一个常量速度,相反可以改方法接收一个block——该block根据具体的时间来定义汽车的速度。
  1. // Car.h
  2. #import <Foundation/Foundation.h>
  3. @interface Car : NSObject
  4. @property double odometer;
  5. - (void)driveForDuration:(double)duration
  6. withVariableSpeed:(double (^)(double time))speedFunction
  7. steps:(int)numSteps;
  8. @end
 
上面代码中block的数据类型是double (^)(double time),也就是说block的调用者需要传递一个double类型的参数,并且该block的返回值为double类型。注意:上面代码中的语法基本与本文开头介绍的block变量声明相同,只不过没有变量名字。
 
在函数的实现里面可以通过speedFunction来调用block。下面的示例通过算法计算出汽车行驶的大约距离。其中steps参数是由调用者确定的一个准确值。
  1. // Car.m
  2. #import "Car.h"
  3. @implementation Car
  4. @synthesize odometer = _odometer;
  5. - (void)driveForDuration:(double)duration
  6. withVariableSpeed:(double (^)(double time))speedFunction
  7. steps:(int)numSteps {
  8. double dt = duration / numSteps;
  9. for (int i=1; i<=numSteps; i++) {
  10. _odometer += speedFunction(i*dt) * dt;
  11. }
  12. }
  13. @end
 
在下面的代码中,有一个main函数,在main函数中block定义在另一个函数的调用过程中。虽然理解其中的语法需要话几秒钟时间,不过这比起另外声明一个函数,再定义withVariableSpeed参数要更加直观。
  1. // main.m
  2. #import <Foundation/Foundation.h>
  3. #import "Car.h"
  4. int main(int argc, const char * argv[]) {
  5. @autoreleasepool {
  6. Car *theCar = [[Car alloc] init];
  7. // Drive for awhile with constant speed of 5.0 m/s
  8. [theCar driveForDuration:10.0
  9. withVariableSpeed:^(double time) {
  10. return 5.0;
  11. } steps:100];
  12. NSLog(@"The car has now driven %.2f meters", theCar.odometer);
  13. // Start accelerating at a rate of 1.0 m/s^2
  14. [theCar driveForDuration:10.0
  15. withVariableSpeed:^(double time) {
  16. return time + 5.0;
  17. } steps:100];
  18. NSLog(@"The car has now driven %.2f meters", theCar.odometer);
  19. }
  20. return 0;
  21. }
 
上面利用一个简单的示例演示了block的通用性。在iOS的SDK中有许多API都利用了block的其它一些功能。NSArray的sortedArrayUsingComparator:方法可以使用一个block对元素进行排序,而UIView的animateWithDuration:animations:方法使用了一个block来定义动画的最终状态。此外,block在并发编程中具有强大的作用。
 
定义Block类型
由于block数据类型的语法会很快把函数的声明搞得难以阅读,所以经常使用typedef对block的签名(signature)做处理。例如,下面的代码创建了一个叫做SpeedFunction的新类型,这样我们就可以对withVariableSpeed参数使用一个更加有语义的数据类型。
  1. // Car.h
  2. #import <Foundation/Foundation.h>
  3. // Define a new type for the block
  4. typedef double (^SpeedFunction)(double);
  5. @interface Car : NSObject
  6. @property double odometer;
  7. - (void)driveForDuration:(double)duration
  8. withVariableSpeed:(SpeedFunction)speedFunction
  9. steps:(int)numSteps;
  10. @end
 
许多标准的Objective-C框架也使用了这样的技巧,例如NSComparator。
 
总结
Block不仅提供了C函数同样的功能,而且block看起来更加直观。block可以定义为内联(inline),这样在函数内部调用的时候就非常方便,由于block具有闭包性(closure),所以block可以很容易获得上下文信息,而又不会对这些数据产生负面影响。
 
延伸阅读

初识block的更多相关文章

  1. Block系列1:初识block

    //-------1.定义函数----- //1.函数 int sum(int a,int b) { return a+b; } //------------------2.声明--------- / ...

  2. iOS开发技巧系列---使用链式编程和Block来实现UIAlertView

    UIAlertView是iOS开发过程中最常用的控件之一,是提醒用户做出选择最主要的工具.在iOS8及后来的系统中,苹果更推荐使用UIAlertController来代替UIAlertView.所以本 ...

  3. [Objective-C] Block实现回调和简单的学习思考

    初识Block的时候,总觉得其很可怕,因为看不懂其运行原理,所以用起来总是觉得不安全.关于Block的语法,等我把手里的资料全部看完,整理好再发出来.这次先看看用Block怎么实现回调. 新博客:wo ...

  4. block的初识

    block的介绍: Block是iOS4.0之后新增的一种语法结构,也称为“闭包(closure)”.  SDK4.0新增的API大量使用了Block.  Block是一个匿名的函数代码块,此代码 ...

  5. 初识Hadoop

    第一部分:              初识Hadoop 一.             谁说大象不能跳舞 业务数据越来越多,用关系型数据库来存储和处理数据越来越感觉吃力,一个查询或者一个导出,要执行很长 ...

  6. Python自动化 【第十八篇】:JavaScript 正则表达式及Django初识

    本节内容 JavaScript 正则表达式 Django初识 正则表达式 1.定义正则表达式 /.../  用于定义正则表达式 /.../g 表示全局匹配 /.../i 表示不区分大小写 /.../m ...

  7. 初识Hadoop入门介绍

    初识hadoop入门介绍 Hadoop一直是我想学习的技术,正巧最近项目组要做电子商城,我就开始研究Hadoop,虽然最后鉴定Hadoop不适用我们的项目,但是我会继续研究下去,技多不压身. < ...

  8. hadoop初识

    搞什么东西之前,第一步是要知道What(是什么),然后是Why(为什么),最后才是How(怎么做).但很多开发的朋友在做了多年项目以后,都习惯是先How,然后What,最后才是Why,这样只会让自己变 ...

  9. IOS之UI -- UITableView -- 1 -- 相关初识

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

随机推荐

  1. 【python自动化学习笔记】

    [python自动化第一篇:python介绍与入门] [python自动化第二篇:python入门] [python自动化第三篇:python入门进阶]      [Python自动化第三篇(2):文 ...

  2. rabbitmq启用和禁用web界面管理插件

    rabbitmq默认安装启动以后,是没有开启web管理界面的,通过rabbitmq-plugins list命令可列出插件的启用和禁用状态. 使用rabbitmq-plugins enable xxx ...

  3. day38 02-Spring快速入门

    Spring的核心是IOC和AOP,其他的什么像SpEL都是对IOC的支持. Spring里面的web指的是它可以使用Spring MVC. 集成指的是整合其他的框架. schema是所有配置文件的约 ...

  4. JavaScript 报错 注释

  5. go 文件操作 io

    package main import ( "fmt" "os" ) func main() { //打开文件 //概念说明: file 的叫法 //1. fi ...

  6. 三、Redis操作

    一.基本介绍 1.简介 Redis是由意大利人Salvatore Sanfilippo(网名:antirez)开发的一款内存高速缓存数据库.Redis全称为:Remote Dictionary Ser ...

  7. css清除浮动各方法与原理

    说到清除浮动的方法,我想网络上应该有不下7,8的方法,介绍这些方法之前,想下为什么清除浮动? 再次回到float这个属性,浮动元素(floats)会被移出文档流,不会影响到块状盒子的布局而只会影响内联 ...

  8. 注解2 --- 自定义 Annotation --- 技术搬运工(尚硅谷)

    定义新的 Annotation 类型使用 @interface 关键字 自定义注解自动继承了java.lang.annotation.Annotation接口 Annotation 的成员变量在 An ...

  9. Percona XtraBackup 完全及增量备份与恢复的方法

    安装及备份.恢复实现 安装:其最新版的软件可从 http://www.percona.com/software/percona-xtrabackup/ 获得.本文基于CentOS6.x的系统,因此,直 ...

  10. fusion-io工具--更换fusion-ioSSD无法识别及升级指引

    centos6.5系统,(含fusion-io共7块盘)库存的fusion-io卡需要升级固件才能识别 现在只认到6个盘 fdisk -l 2>/dev/null | grep 'Disk /d ...