[翻译]Swift编程语言——闭包

闭包

闭包是自己自足的功能代码块,能被传递和使用。Swift的闭包和 C语言和OC中的blocks、其他语言中的lambdas 类似。
在闭包的定义上下文环境中,闭包可以捕获任意的常量或者变量。 This is known as closing over those constants and variables,hence the name “closures”. Swift将全部的内存捕捉的句柄都提供给了你。
NOTE
不要担心不理解捕捉这个概念,后续有专门的capturing Values 章节会详细介绍。
全局和嵌套函数其实是闭包的特殊情况。闭包以下面三种之一的面目出现:
1全局函数是拥有名字不需要捕获任何值的闭包。
2嵌套函数是拥有名字可以捕获所在函数范围内值的闭包。
3闭包表达式是没有名字语法轻量化书写、可以捕获其所在环境上下文内的值的闭包。
Swift的闭包表达式具有干净清晰的风格,with optimizations that encourage brief,clutter-free syntax in common scenarios.有点有:
1根据上下文推断参数和返回值的类型。
2省略单表达式闭包(single-expression closures)的返回值
3简写参数名
4追踪(Trailing )闭包语法

闭包表达式

前面介绍的嵌套函数是在一个大函数中命名和定义的函数。然而有时需要一个简化的版本。这在函数作为参数的时候很必要。
闭包表达式(closure expression)是简单集中的书写内联闭包的一种方式。闭包表达式提供了若干语法优化定义闭包,简约而明晰。
下面会用sorted函数的几个不同实现来说明这些优点。

Sorted 函数

Swif标准库提供了一个叫做sorted的函数,它用来对已知类型的数组进行排序,实现过程是通过传入的排序闭包。当完成排序操作后,storted函数返回一个和原来数组长度、类型一致的新数组,新数组中的元素已经是经过排序后的了。原始的数组不会被sorted函数修改。
下面闭包表达式的例子,使用sorted函数对一个String类型的数组进行字母倒排序。这里是原始的数组:

​let​ ​names​ = [​"Chris"​,​"Alex"​,​"Ewa"​,​"Barry"​,​"Daniella"​]

sorted函数带了两个参数:
1一个已知类型的数组。
2一个闭包:带两个参数(这两个参数和数组内容的类型一致),返回一个布尔值(这个布尔值的意思是在排序时,第一个参数是排在第二个参数的之前还是之后)。进行排序时第一个参数要排在第二个参数之前,闭包返回true,否则返回false。
因为上面的数组存储的是String类型的内容,所以排序闭包需要是这样一个函数类型: (String,String) -> Bool。
一种提供闭包的反噬是书写一个正常的对应类型函数,然后将这个函数作为sorted函数的第二个参数传递:

func​ ​backwards​(​s1​: ​String​,​s2​: ​String​) -> ​Bool​ {
​ ​return​ ​s1​ > ​s2
​}
​var​ ​reversed​ = ​sorted​(​names​,​backwards​)
​// reversed is equal to ["Ewa","Daniella","Chris","Barry","Alex"]

如果第一个字符(s1)比第二个字符串(s2)大,backwards函数返回true,表明在排序后的数组中s1将会出现在s2之前。对于字符串中的字符而言,“大”意味着在字母表中的顺序靠后。这意味着字母B要比字母A大,字符串Tom要比字符串Tim大。这里需要按照字母表倒排序,所以Barry应该在Alex之前。其他的字符串比较也是一样的。
然而这是一个冗长的写法,实际上它就是一个单一表达式函数(a > b).这个例子中,这种写法可以按照闭包表达式语法,写一个简短的内联闭包。

闭包表达式语法

闭包表达式语法通常是这样的:

{ (parameters) -> return type in
    statements
}

闭包表达式语法可以采用常量参数、变量参数、和inout参数。参数的默认值是不能使用的。可变参数可以使用,前提是你给它加了名字而且将其放在参数列表最后。元组当然也可以作为闭包的参数和返回值。
下面是闭包表达式版本的backwards函数:

reversed​ = ​sorted​(​names​,{ (​s1​: ​String​,​s2​: ​String​) -> ​Bool​ ​in
​ ​return​ ​s1​ > ​s2
​})

这里内联闭包的参数和返回类型定义,同backwards的定义一样。这两种情下,他们的类型都是 (s1: String,s2: String) -> Bool。然而内联闭包表达式中,参数和返回类型在花括号中被定义,而不是在外部。
注意这里的闭包体使用in关键字打头。这个关键字意思是闭包的参数和返回类型定义结束了,闭包体内容要开始了。
因为闭包体是如此的简短,所以它可以被写在一行内:

reversed​ = ​sorted​(​names​,​s2​: ​String​) -> ​Bool​ ​in​ ​return​ ​s1​ > ​s2​ } )

这说明,sorted函数的整体调用没有变化。一对参数被包裹在作为整体的参数列表。只不过其中一个参数是一个内联闭包。

从上下文中推断类型

因为排序闭包作为参数使用,所以Swift可以根据sorted函数的第二个参数的类型推测出它的参数和返回值类型。第二个参数的类型是(String,String) -> Bool。这就意味着,(String,String)和 Bool 的类型声明在闭包表达式中可以不出现。因为所有的类型都能推测出来,所以->和包围参数的圆括号都可以省略:

​reversed​ = ​sorted​(​names​,{ ​s1​,​s2​ ​in​ ​return​ ​s1​ > ​s2​ } )

传递一个闭包给一个函数作为内联闭包时,一定能够推断出闭包的所有类型。这样,内联闭包就根本不必要采用完整的写法。
尽管这样,如果你愿意,你仍可以根据你的意愿保留明确的类型说明。如果为了避免给你的代码读者造成困扰这样做是值得的。sorted函数的例子中,使用闭包的目的是(比不使用)更加清晰明了排序的实现,对于代码的的读者而言,假设闭包处理的是String类型的值这种行为是安全的,因为它是协助一个Stirng类型的数组进行排序。

单一表达式闭包省略return

上面的例子中,单一表达式闭包定义时可以省略去写return关键字:
​reversed​ = ​sorted​(​names​,​s2​ ​in​ ​s1​ > ​s2​ } )

参数名的简写

对于内联闭包,Swift提供了简写参数名的写法:使用 0, 1,$2……这样的名称指代闭包的参数。
如果你使用了简写的参数名,那么你可以连参数列表定义也省了,参数的个数和类型都可以被推测出来。in关键字也被省去了,因为闭包体被留下了:

​reversed​ = ​sorted​(​names​,{ ​$0​ > ​$1​ } )

这里 0 1指代的是第一个和第二个String类型的参数。

操作符函数

上例事实上还有一种更简练的写法。Swift的字符串类型支持用>作为一个函数表示大于,返回一个布尔值。这正好符合sorted函数的第二个参数的要求。因此你可以传递大于符号,Swift会推断出你的意图:

​reversed​ = ​sorted​(​names​,>)

更多的操作符做函数的信息,参见相关章节。

Trailing 闭包

如果你想要传递一个闭包表达式给函数的最后一个参数,同时这个闭包又非常长,这时你可以写一个Trailing 闭包作为替代。一个Trailing 闭包是被写在函数圆括号之外(或之后)的闭包表达式:

func​ ​someFunctionThatTakesAClosure​(​closure​: () -> ()) {
​ ​// function body goes here
​}
​
​// here's how you call this function without using a trailing closure:
​
​someFunctionThatTakesAClosure​({
​ ​// closure's body goes here
​})
​
​// here's how you call this function with a trailing closure instead:
​
​someFunctionThatTakesAClosure​() {
​ ​// trailing closure's body goes here
​}

NOTE
如果一个闭包表达式作为一个函数的唯一参数同时这个闭包表达式又是一个Trailing 闭包,这种情况下调用函数时你不需要在函数名字后写一对圆括号了。
上面例子使用Trailing 闭包的写法如下:

​reversed​ = ​sorted​(​names​) { ​$0​ > ​$1​ }

当闭包足够长,不能在一行写完时,Trailing 闭包是非常有用的。有一个例子,Swift的数组类型有一个叫做map的方法,它接受一个闭包作为唯一的参数。对于数组中的每一个元素,都会调用闭包一次,闭包的返回该元素的映射值(或者其他什么的)。具体的映射方式和返回类型由闭包指定。
在对每个数组中的元素应用闭包后,map函数返回一个包括了每个元素对应映射的新数组,两个数组的对应顺序一致。
这里有个例子你可以采用一个trailing闭包使用map函数,根据一个Int类型的数组得到一个String类型的数组。数组[16,58,510]被用来个构造一个新数组[“OneSix”,“FiveEight”,“FiveOneZero”]:

​let​ ​digitNames​ = [
​ ​0​: ​"Zero"​,​1​: ​"One"​,​2​: ​"Two"​,​3​: ​"Three"​,​4​: ​"Four"​,​ ​5​: ​"Five"​,​6​: ​"Six"​,​7​: ​"Seven"​,​8​: ​"Eight"​,​9​: ​"Nine"
​]
​let​ ​numbers​ = [​16​,​58​,​510​]

上面的代码创建了一个用来映射的字典,这个字典关联了整型的数字和对应的英文名字。同时定义了一个整型数组,它将被处理成一个字符串类型的数组。
下面你可以使用numbers数组来创建另一个String数组,通过传递一个闭包表达式(trailing闭包)给数组的map方法。记得在调用number.map时,不需要使用圆括号,因为map函数只有一个参数,这个参数是一个trainling闭包:

let​ ​strings​ = ​numbers​.​map​ {
​ (​var​ ​number​) -> ​String​ ​in
​ ​var​ ​output​ = ​""
​ ​while​ ​number​ > ​0​ {
​ ​output​ = ​digitNames​[​number​ % ​10​]! + ​output
​ ​number​ /= ​10
​ }
​ ​return​ ​output
​}
​// strings is inferred to be of type [String]
​// its value is ["OneSix","FiveEight","FiveOneZero"]

map函数对数组中的每个元素都调用了闭包表达式。你不必指定闭包的参数number的类型,因为它的类型可以从数组的内容类型中推断得到。
这个例子中,闭包的number参数被定义为了一个可修改参数,所以number的值可以在闭包体内被修改,这样就不比在定义一个新的变量来接受number的值了。闭包表达式同样定义了返回的类型是String,意味着保存在映射操作过后存储到新数组中的数据类型是String。
闭包表达式在它被调用时构造了了一个叫做ouput的字符串。接下来会对number取它每一位上数字,在根据这个数字去字典中找对应的英文名字。这个闭包可以将任何一个大于0的正数转成对应的英文。
NOTE
使用下表i访问字典digitNames时,后面跟了一个叹号。因为字典下标在找不到key对应的内容时返回一个可选值。上面的例子中可以确保number % 10做下标的时候字典都有值返回,所以使用叹号表示要强制解包这个可选值。
从digitName字典中渠道的字符串被添加到output之前,从而构造出了对应数字的字符串。(表达式number%10根据16得到6,根据58得到8,根据510得到0)
number变量被处以10,因为是整形,所以舍入后16变成了1,58变成了5,510变成了51.
上面的处理过程知道number/=10 等于0,那时output会被闭包返回,被存储在map函数返回的结果数组中。
上面使用trailing闭包的写法,在函数之后接着就完成了闭包的功能,没有将闭包包裹在函数的圆括号之中,更加整洁。

捕获值

闭包可以在他的定义上下文环境中捕获常量或者变量。闭包体内可以引用和修改这些值,尽管这些常量和变量的原来定义作用域已经不复存在了。
Swift中,最简单可以捕获值的闭包形式就是嵌套函数了,嵌套函数只可以捕获它外层的函数的参数,同时可以捕获外层函数体内的定义的常量和变量。

这里有个例子一个叫做makeIncrementor的函数,它里面含有一个嵌套函数叫做incrementor。incrementor函数从它的环境中捕获了两个值,runningTotal 和amount。捕获了这些值后,makeIncrementor 返回incrementor作为闭包(每次调用会给runningTotal加上amount)

func​ ​makeIncrementor​(​forIncrement​ ​amount​: ​Int​) -> () -> ​Int​ {
​ ​var​ ​runningTotal​ = ​0
​ ​func​ ​incrementor​() -> ​Int​ {
​ ​runningTotal​ += ​amount
​ ​return​ ​runningTotal
​ }
​ ​return​ ​incrementor
​}

makeIncrementor 的返回类型是() -> Int。这意味着它返回一个函数而不是一个简单的值。返回的函数没有返回值,每次被调用时会返回一个 Int值。
makeIncrementor 函数定义了一个叫做runningToatl的整型变量,来存储当前增加 到了多少,并返回该值。这个变量初始化的时候被赋值0。
makeIncrementor 函数只有一个 参数,它的外部名字叫做forIncrement,它的本地名叫做amount。这个参数告诉incrementor函数每次调用时要给runningTotal的值加多少。
makeIncrementor 定义 了一个嵌套函数叫做incrementor,incrementor才实际上是做添加的操作。incrementor函数向runningTotal添加amount,并且返回runningTotal。
单独看这个嵌套函数incrementor,有些不寻常:

func​ ​incrementor​() -> ​Int​ {
​ ​runningTotal​ += ​amount
​ ​return​ ​runningTotal
​}

incrementor 函数没有任何参数,但是却可以在其函数体内使用runningTotal 和amount 。这是因为它有从它外部函数中捕获上面两个参数的技能。
因为incrementor 没有修改amount,incrementor实际上是存储了一份amount的副本。这个值随同incrementor函数被存储。
然而,因为incrementor在每次被调用时都修改了runningTotal的值,所以incrementor 捕获了当前runningTotal 变量的引用而不是它初始值的副本。捕获引用使得runningTotal 不会在makeIncrementor 函数被调用完毕后就消失,使得闭包在下次被调用时runningTotal 仍然有效。
NOTE
Swift决定到底是 捕获引用还是捕获值的副本。你不必给amount或者runningTotal添加额外的说明,来表述他们在闭包内会被如何使用。Swift会管理runningTotal的内存占用,当不再被嵌套函数使用时,将会被清除。
下面是一个调用makIncrementor的例子:

let​ ​incrementByTen​ = ​makeIncrementor​(​forIncrement​: ​10​)

上面给一个叫做incrementByTen的常量赋值一个这样函数(每次调用给runningTotal添加10)。调用它几次后的表现:

incrementByTen​()
​// returns a value of 10
​incrementByTen​()
​// returns a value of 20
​incrementByTen​()
​// returns a value of 30

如果你创建第二个增加函数,它将有自己的对引用的存储,runningTotal的引用和第一个是不同的:

let​ ​incrementBySeven​ = ​makeIncrementor​(​forIncrement​: ​7​)
​incrementBySeven​()
​// returns a value of 7

再次调用第一个增加函数(incrementByTen),它仍会在自身的runningTotal引用基础上增加,不受incrementBySeven被调用的影响:

​incrementByTen​()
​// returns a value of 40

NOTE
如果你给一个类实例的属性赋予了一个闭包,同时那个闭包捕获类实例(通过引用类实例或者类实例的成员),那么就你在类实例和闭包之间创建了了一个强引用循环。Swift使用捕获列表破除这种强引用循环。详见:Strong Reference Cycles for Closures

闭包是引用类型

上面例子中incrementBySeven 和incrementByTen 是常量,但是这些常量可以对捕获到的runningTotal变量进行累加。这是因为函数和闭包是引用类型的。
不管你将一个函数 还是闭包赋值给一个常量或变量,你其实做的是将函数/闭包的引用赋值给了常量/变量。上面的例子中,是将incrementByTen闭包指向了那个常量,而不是闭包自身内容。
这同时也意味着如果你将一个闭包赋值给两个不同的变量/常量,这两个变量/常量指向的是一个闭包:

​let​ ​alsoIncrementByTen​ = ​incrementByTen
​alsoIncrementByTen​()
​// returns a value of 50

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。