参考Swift 官方教程《The Swift Programming Language》中文版

闭包(Closures)


闭包是自包含的函数代码块,可以在代码中被传递和使用。 Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 lambdas 函数比较相似。

闭包可以捕获和存储其所在上下文中任意常量和变量的引用。 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作。

注意: 如果您不熟悉捕获(capturing)这个概念也不用担心,您可以在值捕获章节对其进行详细了解。

在函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:

  • 全局函数是一个有名字但不会捕获任何值的闭包
  • 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
  • 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:

  • 利用上下文推断参数和返回值类型
  • 隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
  • 参数名称缩写
  • 尾随(Trailing)闭包语法

闭包表达式(Closure Expressions)

嵌套函数是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在您处理一些函数并需要将另外一些函数作为该函数的参数时。

闭包表达式是一种利用简洁语法构建内联闭包的方式。 闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。 下面闭包表达式的例子通过使用几次迭代展示了sorted函数定义和语法优化的方式。 每一次迭代都用更简洁的方式描述了相同的功能。

sorted 函数(The Sorted Function)

Swift 标准库提供了sorted函数,会根据您提供的基于输出类型排序的闭包函数将已知类型数组中的值进行排序。 一旦排序完成,函数会返回一个与原数组大小相同的新数组,该数组中包含已经正确排序的同类型元素。

下面的闭包表达式示例使用sorted函数对一个String类型的数组进行字母逆序排序,以下是初始数组值:

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

sorted函数需要传入两个参数:

  • 已知类型的数组
  • 闭包函数,该闭包函数需要传入与数组类型相同的两个值,并返回一个布尔类型值来告诉sorted函数当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回true,反之返回false

该例子对一个String类型的数组进行排序,因此排序闭包函数类型需为(String,String) -> Bool

提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为sort函数的第二个参数传入:

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

如果第一个字符串 (s1) 大于第二个字符串 (s2),backwards函数返回true,表示在新的数组中s1应该出现在s2前。 对于字符串中的字符来说,“大于” 表示 “按照字母顺序较晚出现”。 这意味着字母"B"大于字母"A",字符串"Tom"大于字符串"Tim"。 其将进行字母逆序排序,"Barry"将会排在"Alex"之前。

然而,这是一个相当冗长的方式,本质上只是写了一个单表达式函数 (a > b)。 在下面的例子中,利用闭合表达式语法可以更好的构造一个内联排序闭包。

闭包表达式语法(Closure Expression Syntax)

闭包表达式语法有如下一般形式:

{ (parameters) -> returnType 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函数的整体调用保持不变,一对圆括号仍然包裹住了函数中整个参数集合。而其中一个参数现在变成了内联闭包(相比于backwards版本的代码)。

根据上下文推断类型(Inferring Type From Context)

因为排序闭包函数是作为sorted函数的参数进行传入的,Swift可以推断其参数和返回值的类型。sorted期望第二个参数是类型为(String,String) -> Bool的函数,因此实际上String,StringBool类型并不需要作为闭包表达式定义中的一部分。 因为所有的类型都可以被正确推断,返回箭头 (->) 和围绕在参数周围的括号也可以被省略:

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

实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。

单表达式闭包隐式返回(Implicit Return From Single-Expression Clossures)

单行表达式闭包可以通过隐藏return关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:

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

在这个例子中,sorted函数的第二个参数函数类型明确了闭包必须返回一个Bool类型值。 因为闭包函数体只包含了一个单一表达式 (s1 > s2),该表达式返回Bool类型值,因此这里没有歧义,return关键字可以省略。

参数名称缩写(Shorthand Argument Names)

Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数。

如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

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

在这个例子中,$0$1表示闭包中第一个和第二个String类型的参数。

运算符函数(Operator Functions)

实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。 Swift 的String类型定义了关于大于号 (>) 的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。 而这正好与sorted函数的第二个参数需要的函数类型相符合。 因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现:

reversed = sorted(names,>)

更多关于运算符表达式的内容请查看运算符函数。

尾随闭包(Trailing Closures)

如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。 尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。

func someFunctionThatTakesAClosure(closure: () -> ()) {
    // 函数体部分
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure({
    // 闭包主体部分
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
  // 闭包主体部分
}

注意: 如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉。

在上例中作为sorted函数参数的字符串排序闭包可以改写为:

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

当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。 举例来说,Swift 的Array类型有一个map方法,其获取一个闭包表达式作为其唯一参数。 数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。 具体的映射方式和返回值类型由闭包来指定。

当提供给数组闭包函数后,map方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。

下例介绍了如何在map方法中使用尾随闭包将Int类型数组[16,58,510]转换为包含对应String类型的数组["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]

如上代码创建了一个数字位和它们名字映射的英文版本字典。 同时定义了一个准备转换为字符串的整型数组。

您现在可以通过传递一个尾随闭包给numbersmap方法来创建对应的字符串版本数组。 需要注意的时调用numbers.map不需要在map后面包含任何括号,因为其只需要传递闭包表达式这一个参数,并且该闭包表达式参数通过尾随方式进行撰写:

let strings = numbers.map {
    (var number) -> String in
    var output = ""
    while number > 0 {
        output = digitNames[number % 10]! + output
        number /= 10
    }
    return output
}
// strings 常量被推断为字符串类型数组,即 String[]
// 其值为 ["Onesix","FiveOneZero"]

map在数组中为每一个元素调用了闭包表达式。 您不需要指定闭包的输入参数number的类型,因为可以通过要映射的数组类型进行推断。

闭包number参数被声明为一个变量参数(变量的具体描述请参看常量参数和变量参数),因此可以在闭包函数体内对其进行修改。闭包表达式制定了返回类型为String,以表明存储映射值的新数组类型为String

闭包表达式在每次被调用的时候创建了一个字符串并返回。 其使用求余运算符 (number % 10) 计算最后一位数字并利用digitNames字典获取所映射的字符串。

注意: 字典digitNames下标后跟着一个叹号 (!),因为字典下标返回一个可选值 (optional value),表明即使该 key 不存在也不会查找失败。 在上例中,它保证了number % 10可以总是作为一个digitNames字典的有效下标 key。 因此叹号可以用于强制解析 (force-unwrap) 存储在可选下标项中的String类型值。

digitNames字典中获取的字符串被添加到输出的前部,逆序建立了一个字符串版本的数字。 (在表达式number % 10中,如果number为16,则返回6,58返回8,510返回0)。

number变量之后除以10。 因为其是整数,在计算过程中未除尽部分被忽略。 因此 16变成了1,58变成了5,510变成了51。

整个过程重复进行,直到number /= 10为0,这时闭包会将字符串输出,而map函数则会将字符串添加到所映射的数组中。

上例中尾随闭包语法在函数后整洁封装了具体的闭包功能,而不再需要将整个闭包包裹在map函数的括号内。

捕获值(Capturing Values)

闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。

下例为一个叫做makeIncrementor的函数,其包含了一个叫做incrementor嵌套函数。 嵌套函数incrementor从上下文中捕获了两个值,runningTotalamount。 之后makeIncrementorincrementor作为闭包返回。 每次调用incrementor时,其会以amount作为增量增加runningTotal的值。

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

makeIncrementor返回类型为() -> Int。 这意味着其返回的是一个函数,而不是一个简单类型值。 该函数在每次调用时不接受参数只返回一个Int类型的值。 关于函数返回其他函数的内容,请查看函数类型作为返回类型。

makeIncrementor函数定义了一个整型变量runningTotal(初始为0) 用来存储当前跑步总数。 该值通过incrementor返回。

makeIncrementor有一个Int类型的参数,其外部命名为forIncrement, 内部命名为amount,表示每次incrementor被调用时runningTotal将要增加的量。

incrementor函数用来执行实际的增加操作。 该函数简单地使runningTotal增加amount,并将其返回。

如果我们单独看这个函数,会发现看上去不同寻常:

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

incrementor函数并没有获取任何参数,但是在函数体内访问了runningTotalamount变量。这是因为其通过捕获在包含它的函数体内已经存在的runningTotalamount变量而实现。

由于没有修改amount变量,incrementor实际上捕获并存储了该变量的一个副本,而该副本随着incrementor一同被存储。

然而,因为每次调用该函数的时候都会修改runningTotal的值,incrementor捕获了当前runningTotal变量的引用,而不是仅仅复制该变量的初始值。捕获一个引用保证了当makeIncrementor结束时候并不会消失,也保证了当下一次执行incrementor函数时,runningTotal可以继续增加。

注意: Swift 会决定捕获引用还是拷贝值。 您不需要标注amount或者runningTotal来声明在嵌入的incrementor函数中的使用方式。 Swift 同时也处理runingTotal变量的内存管理操作,如果不再被incrementor函数使用,则会被清除。

下面代码为一个使用makeIncrementor的例子:

let incrementByTen = makeIncrementor(forIncrement: 10)

该例子定义了一个叫做incrementByTen的常量,该常量指向一个每次调用会加10的incrementor函数。 调用这个函数多次可以得到以下结果:

incrementByTen()
// 返回的值为10
incrementByTen()
// 返回的值为20
incrementByTen()
// 返回的值为30

如果您创建了另一个incrementor,其会有一个属于自己的独立的runningTotal变量的引用。 下面的例子中,incrementBySevne捕获了一个新的runningTotal变量,该变量和incrementByTen中捕获的变量没有任何联系:

let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
// 返回的值为7
incrementByTen()
// 返回的值为40

注意: 如果您将闭包赋值给一个类实例的属性,并且该闭包通过指向该实例或其成员来捕获了该实例,您将创建一个在闭包和实例间的强引用环。 Swift 使用捕获列表来打破这种强引用环。更多信息,请参考闭包引起的循环强引用。

闭包是引用类型(Closures Are Reference Types)

上面的例子中,incrementBySevenincrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。 这是因为函数和闭包都是引用类型。

无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。 上面的例子中,incrementByTen指向闭包的引用是一个常量,而并非闭包内容本身。

这也意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回的值为50

Swift学习:2.7 闭包的更多相关文章

  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. xamarin – 崩溃在AccountStore.Create().保存(e.Account,“);

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

  9. ios – 将视频分享到Facebook

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

  10. xcode – 错误“线程1:断点2.1”

    我正在研究RESTAPI管理器.这是一个错误,我无法解决它.我得到的错误在下面突出显示.当我打电话给这个班级获取资源时:我评论的线打印:Thread1:breakpoint2.1我需要修复错误的建议.任何建议都非常感谢解决方法您可能在不注意的情况下意外设置了断点.单击并拖动代表断路器外部断点的蓝色刻度线以将其擦除.

随机推荐

  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,所以编译器会报错,现在来一一解决。

返回
顶部