- 闭包是功能性自包含模块,可以在代码中被传递和使用。 Swift 中的闭包与 C 和Objective-C 中的 blocks 以及其他一些编程语言中的 lambdas 比较相似。
- 闭包可以捕获和存储其所在上下文中任意常量和变量的引用。 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的内存操作。
- 在 函数 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
- 全局函数是一个有名字但不会捕获任何值的闭包
- 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
- 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的没有名字的闭包
闭包表达式
闭包表达式是一种利用简洁语法构建内联闭包的方式。 闭包表达式供了一些语法优化,使得撰写闭包变得简单明了。 下面闭包表达式的例子通过使用几次迭代展示了 sort 函数定义和语法优化的方式。 每一次迭代都用更简洁的方式述了相同的功能。
sort 函数
下面的闭包表达式示例使用 sort 函数对一个 String 类型的数组进行字母逆序排序,以下是初始数组值:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
排序函数有两个参数:
- 已知类型值的数组。
- 一个闭包,采用相同类型的数组的内容的两个参数,并返回一个布尔值来表示是否将第一个值在排序时放到第二个值的前面或是后面。如果第一个值应该出现第二个值之前,闭包需要返回 true,否则返回 false。
func backwards(s1: String, s2: String) -> Bool {2. return s1 > s2.
}
var reversed = sort(names, backwards)5.
// reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
闭包表达式语法
闭包表达式语法有如下一般形式:
{ (parameters) -> returnType in
statements
}
下面的例子展示了之前 backwards 函数对应的闭包表达式版本的代码:
reversed = sort(names, { (s1: String, s2: String) -> Bool in
return s1 > s2
})
根据上下文推断类型
因为排序闭包是作为函数的参数进行传入的,Swift 可以推断其参数和返回值的类型。 sort期望第二个参数是类型为 (String, String) -> Bool 的函数,因此实际上 String, String 和Bool 类型并不需要作为闭包表达式定义中的一部分。 因为所有的类型都可以被正确推断,返回箭头 (->) 和 围绕在参数周围的括号也可以被省略:
reversed = sort(names, { s1, s2 in return s1 > s2 } )
单行表达式闭包可以省略 return
reversed = sort(names, { s1, s2 in s1 > s2 } )
参数名简写
如果您在闭包表达式中使用参数名称简写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称简写的类型会通过函数类型进行推断。 in 关键字也同样可以被省略,因为此时闭包表达式
reversed = sort(names, { $0 > $1 } )
//$0 和 $1 表示闭包中第一个和第二个 String 类型的参数。
运算符函数
实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。 Swift 的 String 类型定义了关于大于号 (>) 的字符串实现,让其作为一个函数接受两个 String 类型的参数并返回Bool 类型的值。而这正好与 sort 函数的第二个参数需要的函数类型相符合。因此,您可以简单地传递一个大于号,Swift 可以自动推断出您想使用大于号的字符串函数实现:
reversed = sort(names, >)
Trailing 闭包
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用 trailing 闭包来增强函数的可读性。
Trailing 闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其作为最后一个参数调用。
func someFunctionThatTakesAClosure(closure: () -> ()) {
// 函数体部分
}
// 以下是不使用 trailing 闭包进行函数调用
someFunctionThatTakesAClosure({
// 闭包主体部分
})// 以下是使用 trailing 闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
捕获 (Caputure)
闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift 最简单的闭包形式是嵌套函数,也就是定义在其他函数体内的函数。 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
下例为一个叫做 makeIncrementor 的函数,其包含了一个叫做 incrementor 嵌套函数。嵌套函数 incrementor 从上下文中捕获了两个值,runningTotal 和 amount。 之后makeIncrementor 将 incrementor 作为闭包返回。 每次调用 incrementor 时,其会以amount 作为增量增加 runningTotal 的值。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
incrementor 函数并没有获取任何参数,但是在函数体内访问了 runningTotal 和 amount变量。这是因为其通过捕获在包含它的函数体内已经存在的 runningTotal 和 amount 变量
而实现。
由于没有修改 amount 变量,incrementor 实际上捕获并存储了该变量的一个副本,而该副本随着 incrementor 一同被存储。
然而,因为每次调用该函数的时候都会修改 runningTotal 的值,incrementor 捕获了当前runningTotal 变量的引用,而不是仅仅复制该变量的初始值。捕获一个引用保证了当makeIncrementor 结束时候并不会消失,也保证了当下一次执行 incrementor 函数时,runningTotal 可以继续增加。
下面为一个使用 makeIncrementor 的例子:
let incrementByTen = makeIncrementor(forIncrement: 10)
该例子定义了一个叫做 incrementByTen 的常量,该常量指向一个每次调用会加 10 的incrementor 函数。 调用这个函数多次可以得到以下结果:
incrementByTen()
// 返回的值为10
incrementByTen()
// 返回的值为205. incrementByTen()
// 返回的值为30
如果您创建了另一个 incrementor,其会有一个属于自己的独立的 runningTotal 变量的引用。 下面的例子中,incrementBySevne 捕获了一个新的 runningTotal 变量,该变量和incrementByTen 中捕获的变量没有任何联系:
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()3. // 返回的值为74. incrementByTen()
// 返回的值为40