如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,将这个闭包替换成为尾随闭包的形式很有用。尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
在 8.2. 闭包表达式 章节中的字符串排序闭包可以作为尾随包的形式改写在 sorted(by:)
方法圆括号的外面:
reversedNames = names.sorted() { $0 > $1 }
如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 ()
省略掉:
reversedNames = names.sorted { $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]
如上代码创建了一个整型数位和它们英文版本名字相映射的字典。同时还定义了一个准备转换为字符串数组的整型数组。
你现在可以通过传递一个尾随闭包给 numbers
数组的 map(_:)
方法来创建对应的字符串版本数组:
let strings = numbers.map {
(number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// strings 常量被推断为字符串类型数组,即 [String]
// 其值为 ["OneSix", "FiveEight", "FiveOneZero"]
map(_:)
为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数 number
的类型,因为可以通过要映射的数组类型进行推断。
在该例中,局部变量 number
的值由闭包中的 number
参数获得,因此可以在闭包函数体内对其进行修改,(闭包或者函数的参数总是常量),闭包表达式指定了返回类型为 String
,以表明存储映射值的新数组类型为 String
。
闭包表达式在每次被调用的时候创建了一个叫做 output
的字符串并返回。其使用求余运算符(number % 10
)计算最后一位数字并利用 digitNames
字典获取所映射的字符串。这个闭包能够用于创建任意正整数的字符串表示。
注意:字典
digitNames
下标后跟着一个叹号(!
),因为字典下标返回一个可选值(optional value),表明该键不存在时会查找失败。在上例中,由于可以确定number % 10
总是digitNames
字典的有效下标,因此叹号可以用于强制解包(force-unwrap)存储在下标的可选类型的返回值中的String
类型的值。
从 digitNames
字典中获取的字符串被添加到 output
的前部,逆序建立了一个字符串版本的数字。(在表达式 number % 10
中,如果 number
为 16
,则返回 6
,58
返回 8
,510
返回 0
。)
number
变量之后除以 10
。因为其是整数,在计算过程中未除尽部分被忽略。因此 16
变成了 1
,58
变成了 5
,510
变成了 51
。
整个过程重复进行,直到 number /= 10
为 0
,这时闭包会将字符串 output
返回,而 map(_:)
方法则会将字符串添加到映射数组中。
在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 map(_:)
方法的括号内。
下一节:什么是值捕获?
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。