Swift中的闭包

一、简介

闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。
Swift 中的闭包与 COC 中的代码块(blocks)以及其他一些编程语言中的 匿名函数 比较相似。全局函数和嵌套函数其实就是特殊的闭包。
由于之前对 Swift 中的闭包不太熟悉,所以在此归纳总结一下闭包的语法。

二、语法

Swift 中的闭包有很多优化的地方:

  • 根据上下文推断参数和返回值类型
  • 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略 return
  • 可以使用简化参数名,如$0, $1(从 0 开始,表示第 i 个参数...)
  • 提供了尾随闭包语法(Trailing closure syntax)

闭包表达式 提供了一些语法优化,使得撰写闭包变得简单明了。下面 闭包表达式 的例子通过使用几次迭代展示了 sorted(by:) 方法定义和语法优化的方式。下面的每一次迭代都用更简洁的方式描述了相同的功能。->->->->->->。

  • sorted(by:) 函数介绍:
    Swift 标准库提供了名为 sorted(by:) 的方法,它会根据你所提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,sorted(by:) 方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被 sorted(by:) 方法修改。

下面的闭包表达式示例使用 sorted(by:)方法对一个 String 类型的数组进行字母逆序排序。以下是初始数组:

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

sorted(by:)方法接受一个 闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回 true,反之返回 false

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

原始实现方式:

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

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// 打印可得 reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

然而,以这种方式来编写一个实际上很简单的表达式(a > b),确实太过繁琐了。对于这个例子来说,利用 闭包表达式语法 可以更好地构造一个 内联排序闭包

闭包表达式语法:

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

{ (parameters) -> return type in
    statements
}

闭包表达式参数 可以是 in-out 参数,但不能设定默认值。如果你命名了可变参数,也可以使用此可变参数。元组也可以作为参数和返回值。

第一次精简——闭包语法:

下面的例子展示了之前 backward(_:_:) 函数对应的闭包表达式版本的代码:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

需要注意的是内联闭包参数和返回值类型声明与 backward(_:_:) 函数类型声明相同。在这两种方式中,都写成了 (s1: String, s2: String) -> Bool。然而在内联闭包表达式中,函数和返回值类型都写在大括号 ,而不是大括号

  • 关键字 in:闭包的函数体部分由关键字 in 引入。该关键字表示 “闭包的参数和返回值类型定义已经完成,闭包函数体即将开始”

该例中 sorted(by:) 方法的整体调用保持不变,一对圆括号仍然包裹住了方法的整个参数。然而,参数现在变成了 内联闭包

第二次精简——根据上下文推断类型:

因为排序闭包函数是作为 sorted(by:) 方法的参数传入的,Swift 可以推断其参数和返回值的类型。sorted(by:) 方法被一个字符串数组调用,因此其参数必须是 (String, String) -> Bool 类型的函数。这意味着 (String, String)Bool 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

实际上,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,总是能够推断出闭包的参数和返回值类型。这意味着闭包作为函数或者方法的参数时,你几乎不需要利用完整格式构造内联闭包。

第三次精简——单表达式闭包隐式返回:

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

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

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

第四次精简——参数名称缩写:

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

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

reversedNames = names.sorted(by: { $0 > $1 } )

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

第五次精简——运算符方法:

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

reversedNames = names.sorted(by: >)

第六次精简——尾随闭包:

  • 什么是尾随闭包?
    如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用 尾随闭包 来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
func someFunctionThatTakesAClosure(closure: () -> Void) {
   // 函数体部分
}

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

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

在上面 sorted(by:) 方法参数的字符串排序闭包可以改写为:

reversedNames = names.sorted() { $0 > $1 }

如果闭包表达式是函数或方法的唯一参数,则当使用尾随闭包时,甚至可以把 () 省略掉:

reversedNames = names.sorted { $0 > $1 }

以上只是列举了闭包的一些基本语法与用法,还有一些其他概念需要继续学习,如 自动闭包、逃逸闭包,后续我会慢慢补齐总结的,谢谢!

以上的总结参考了并部分摘抄了以下文章,非常感谢以下作者的分享!:
《The Swift Programming Language 中文版》

转载请备注原文出处,不得用于商业传播——凡几多

你可能感兴趣的:(Swift中的闭包)