闭包

闭包是自己自足的功能代码块,能被传递和使用。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

[翻译]Swift编程语言——闭包的更多相关文章

  1. HTML5 WebSocket实现点对点聊天的示例代码

    这篇文章主要介绍了HTML5 WebSocket实现点对点聊天的示例代码的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

  3. ios – 仅在异步函数完成执行后运行代码

    所以,例如:如果问题是你不知道要调用什么函数,你可以配置你周围的函数/对象,这样有人可以给你一个函数,然后你在我上面说“调用函数”的地方调用你的函数.例如:

  4. ios – 在Swift中将输入字段字符串转换为Int

    所以我非常擅长制作APP广告Swift,我试图在文本字段中做一些非常简单的输入,取值,然后将它们用作Int进行某些计算.但是’vardistance’有些东西不正确它是导致错误的最后一行代码.它说致命错误:无法解开Optional.None解决方法在你的例子中,距离是一个Int?否则称为可选的Int..toInt()返回Int?因为从String到Int的转换可能失败.请参阅以下示例:

  5. ios – Swift相当于`[NSDictionary initWithObjects:forKeys:]`

    Swift的原生字典是否与[NSDictionaryinitWithObjects:forKeys:]相当?假设我有两个带键和值的数组,并希望将它们放在字典中.在Objective-C中,我这样做:当然我可以通过两个数组迭代一个计数器,使用vardict:[String:Int]并逐步添加东西.但这似乎不是一个好的解决方案.使用zip和enumerate可能是同时迭代两者的更好方法.然而,这种方法

  6. 如何在iOS中检测文本(字符串)语言?

    例如,给定以下字符串:我想检测每个声明的字符串中使用的语言.让我们假设已实现函数的签名是:如果没有检测到语言,则返回可选字符串.因此,适当的结果将是:有一个简单的方法来实现它吗?

  7. ios – 如何使用Objective C类中的多个参数调用Swift函数?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  8. ios – Swift 4添加手势:覆盖vs @objc

    我想在我的视图中添加一个手势,如下所示:但是,在Swift4中,我的编译器给出了以下错误:建议添加@objc以将此实例方法公开给Objective-C.实现此目的的另一个选项将覆盖touchesBegan()函数并使用它来处理点击.我试图以“Swift”的方式做到这一点,而不必带入Obj-C.有没有纯粹的Swift方式来添加这个轻击手势而不使用@objc?

  9. xamarin – 崩溃在AccountStore.Create().保存(e.Account,“);

    在Xamarin.Forms示例TodoAwsAuth中https://developer.xamarin.com/guides/xamarin-forms/web-services/authentication/oauth/成功登录后,在aOnAuthenticationCompleted事件中,应用程序在尝试保存到Xamarin.Auth时崩溃错误说不能对钥匙串说期待着寻求帮助.解决方法看看你

  10. ios – 将视频分享到Facebook

    我正在编写一个简单的测试应用程序,用于将视频从iOS上传到Facebook.由于FacebookSDK的所有文档都在Objective-C中,因此我发现很难在线找到有关如何使用Swift执行此操作的示例/教程.到目前为止我有这个在我的UI上放置一个共享按钮,但它看起来已禁用,从我读到的这是因为没有内容设置,但我看不出这是怎么可能的.我的getVideoURL()函数返回一个NSURL,它肯定包含视

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部