初识block-转自CocoaChina

2024-04-23 21:18
文章标签 初识 转自 block cocoachina

本文主要是介绍初识block-转自CocoaChina,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文转自破船的博客:

小引

本周末微博上朋友发了一个关于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.   
  4. int main(int argc, const char * argv[]) { 
  5.     @autoreleasepool { 
  6.         // Declare the block variable 
  7.         double (^distanceFromRateAndTime)(double rate, double time); 
  8.   
  9.         // Create and assign the block 
  10.         distanceFromRateAndTime = ^double(double rate, double time) { 
  11.             return rate * time; 
  12.         }; 
  13.         // Call the block 
  14.         double dx = distanceFromRateAndTime(35, 1.5); 
  15.   
  16.         NSLog(@"A car driving 35 mph will travel " 
  17.               @"%.2f miles in 1.5 hours.", dx); 
  18.     } 
  19.     return 0; 
在上面的代码中,利用插入符(^)将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.   
  7. // Try changing the non-local variable (it won't change the block) 
  8. make = @"Porsche"
  9. 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.   
  4. @interface Car : NSObject 
  5.   
  6. @property double odometer; 
  7.   
  8. - (void)driveForDuration:(double)duration 
  9.        withVariableSpeed:(double (^)(double time))speedFunction 
  10.                    steps:(int)numSteps; 
  11.   
  12. @end 
上面代码中block的数据类型是double (^)(double time),也就是说block的调用者需要传递一个double类型的参数,并且该block的返回值为double类型。注意:上面代码中的语法基本与本文开头介绍的block变量声明相同,只不过没有变量名字。
在函数的实现里面可以通过speedFunction来调用block。下面的示例通过算法计算出汽车行驶的大约距离。其中steps参数是由调用者确定的一个准确值。
 
  1. // Car.m 
  2. #import "Car.h" 
  3.   
  4. @implementation Car 
  5.   
  6. @synthesize odometer = _odometer; 
  7.   
  8. - (void)driveForDuration:(double)duration 
  9.        withVariableSpeed:(double (^)(double time))speedFunction 
  10.                    steps:(int)numSteps { 
  11.     double dt = duration / numSteps; 
  12.     for (int i=1; i<=numSteps; i++) { 
  13.         _odometer += speedFunction(i*dt) * dt; 
  14.     } 
  15.   
  16. @end 
在下面的代码中,有一个main函数,在main函数中block定义在另一个函数的调用过程中。虽然理解其中的语法需要话几秒钟时间,不过这比起另外声明一个函数,再定义withVariableSpeed参数要更加直观。
 
  1. // main.m 
  2. #import <Foundation/Foundation.h> 
  3. #import "Car.h" 
  4.   
  5. int main(int argc, const char * argv[]) { 
  6.     @autoreleasepool { 
  7.         Car *theCar = [[Car alloc] init]; 
  8.   
  9.         // Drive for awhile with constant speed of 5.0 m/s 
  10.         [theCar driveForDuration:10.0 
  11.                withVariableSpeed:^(double time) { 
  12.                            return 5.0; 
  13.                        } steps:100]; 
  14.         NSLog(@"The car has now driven %.2f meters", theCar.odometer); 
  15.   
  16.         // Start accelerating at a rate of 1.0 m/s^2 
  17.         [theCar driveForDuration:10.0 
  18.                withVariableSpeed:^(double time) { 
  19.                            return time + 5.0; 
  20.                        } steps:100]; 
  21.         NSLog(@"The car has now driven %.2f meters", theCar.odometer); 
  22.     } 
  23.     return 0; 
上面利用一个简单的示例演示了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.   
  4. // Define a new type for the block 
  5. typedef double (^SpeedFunction)(double); 
  6.   
  7. @interface Car : NSObject 
  8.   
  9. @property double odometer; 
  10.   
  11. - (void)driveForDuration:(double)duration 
  12.        withVariableSpeed:(SpeedFunction)speedFunction 
  13.                    steps:(int)numSteps; 
  14.   
  15. @end 
许多标准的Objective-C框架也使用了这样的技巧,例如NSComparator。
总结
Block不仅提供了C函数同样的功能,而且block看起来更加直观。block可以定义为内联(inline),这样在函数内部调用的时候就非常方便,由于block具有闭包性(closure),所以block可以很容易获得上下文信息,而又不会对这些数据产生负面影响。

原文出处:http://www.cocoachina.com/applenews/devnews/2013/0710/6569.html

这篇关于初识block-转自CocoaChina的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/929914

相关文章

Linux操作系统 初识

在认识操作系统之前,我们首先来了解一下计算机的发展: 计算机的发展 世界上第一台计算机名叫埃尼阿克,诞生在1945年2月14日,用于军事用途。 后来因为计算机的优势和潜力巨大,计算机开始飞速发展,并产生了一个当时一直有效的定律:摩尔定律--当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。 那么相应的,计算机就会变得越来越快,越来越小型化。

docker学习系列(一)初识docker

在第一版本上线之后公司,我们决定将之前使用的开源api文档项目转移到本公司的服务器之上,之前用的是showdoc,showdoc利用的是php技术,作为java程序员表示需要快速部署php环境以及apach容器都需要时间,所以采用第二种方法,即利用docker进行快速部署(虽然学习成本也不比php少)。 一、docker简介 docker的官网是https://www.docker.com,

[Linux Kernel Block Layer第一篇] block layer架构设计

目录 1. single queue架构 2. multi-queue架构(blk-mq)  3. 问题 随着SSD快速存储设备的发展,内核社区越发发现,存储的性能瓶颈从硬件存储设备转移到了内核block layer,主要因为当时的内核block layer是single hw queue的架构,导致cpu锁竞争问题严重,本文先提纲挈领的介绍内核block layer的架构演进,然

框架template初识

框架初识 框架就是一个别人帮我们搭好的舞台,造好了很多现成的工具供我们使用,让开发过程更快速、简洁。 Gin框架介绍 Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 Gin是一个用Go语言编写的web框架。它是一个类似于martini 但拥有更好性能的API框架, 由于使用了 httprouter,速度提高了近40倍。 第一个Gin示例 package mai

【数据结构】--初识泛型

1. 包装类 在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。 1.1 基本数据类型和对应的包装类 除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。 1.2 (自动)装箱和(自动)拆箱 装箱(装包): 把 基本数据类型 变为 包装类类型 的过程 叫做装箱。 反汇编指

初识Linux · 进度条

目录 前言: 1 缓冲区和回车换行 2 进度条 前言: 我们目前学习了些许知识,已经足够支持我们写一个非常非常小的项目了,即进度条,相信大家都有过下载游戏,等待游戏更新完成的时候,那么此时就有一个进度条,代表着游戏的更新进度,那么我们今天就来模拟实现这个过程,在此之前,我们需要一些预备知识。 1 缓冲区和回车换行 回车换行?是的,你没有看错,相信不少人对换行有一定的误解,我们

block对变量捕获的方式

之前见很多文章对block捕获变量的方法,会进行诸如此类的描述:“block会捕获被引用的变量, 并对其进行copy操作, 因此, 可能会导致其引用计数加1,如果处理不好, 可能因循环引用导致内存泄漏。” 实际上, 这种说法并不严谨。block对变量的捕获, 根据变量类型的不同,会采用不同的捕获方式。 (1)静态或者全局变量, 在block中直接是指针传递的方式传入block中,对其进行的操作

Linux初识线程

前言 前面在介绍进程的时候,说过进程的内核表述是"进程是承担资源分配的基本实体",但是我们至今都没有介绍如何理解他?本期我们就会介绍! 目录 前言 一、再谈地址空间和页表 1、OS对物理内存的管理 • 为什么4KB是OS进行I/O的基本单位? 2、再谈页表 • 二级页表 • 如何找到一个变量的所有字节? • 虚拟地址是如何转为物理地址的? • 理解动态内存管理 • 为什么对

Linux block_device gendisk和hd_struct到底是个啥关系

本文的源码版本是Linux 5.15版本,有图有真相: 1.先从块设备驱动说起 安卓平台有一个非常典型和重要的块设备驱动:zram,我们来看一下zram这个块设备驱动加载初始化和swapon的逻辑,完整梳理完这个逻辑将对Linux块设备驱动模型有深入的理解。 zram驱动加载的时候会调用zram_add函数,源码如下: 1887/*1888 * Allocate and initia

初识命名空间

1.创建两个命名空间 ip netns add host1 ip netns add host2 2.  查看命名空间 ip netns ls 3 、 创建veth ip -netns host1 link add veth0 type veth peer name host1-peer 4、 查看命名空间接口  ip -netns host1 address 5、 把hos