尾随闭包
本教程共 93 篇 · 第 35 篇 · 更新于 2026-08-08 · 约 7 分钟阅读
本节目标:理解尾随闭包这种语法糖,能把它从函数括号里挪到外面,并掌握多个尾随闭包的写法。
上一章我们学会了闭包表达式的完整写法,也看到了各种简化。但有些闭包逻辑比较长,硬塞在函数最后一个参数的小括号里,读起来像一堆乱麻。Swift 给这种场景准备了一颗糖——尾随闭包(trailing closure)。它不改变任何语义,纯粹为了让代码更好读。
1-1 什么时候用尾随闭包
规则很简单:当闭包表达式是函数的最后一个参数,而且闭包比较长时,你可以把这段闭包写在函数调用那一堆小括号的外面。闭包虽然写在外面,它仍然还是那个函数的参数,这一点没有任何变化。
先看不用尾随闭包时怎么调用一个接收闭包的函数:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体
}
someFunctionThatTakesAClosure(closure: {
// 闭包体
})
换成尾随闭包的写法,把闭包挪到括号外面,并且不再写参数标签 closure::
someFunctionThatTakesAClosure() {
// 尾随闭包体
}
注意看,函数名后面的小括号里空了,闭包跟在它后面。编译器完全清楚这段闭包就是那个 closure 参数。这种写法在闭包较长时特别清爽,因为闭包不再被挤在参数列表里。
1-2 唯一参数时连括号都省
还有一个更进一步的便利:如果闭包是函数的唯一参数,那么函数名后面那对空小括号 () 也可以整个省掉。把上一章的倒序排序用尾随闭包重写一遍:
reversedNames = names.sorted() { $0 > $1 }
因为 sorted(by:) 只接收这一个闭包参数,小括号都能去掉,变成:
reversedNames = names.sorted { $0 > $1 }
这就是你在 Swift 代码里最常见的形态:sorted { ... }。日常写代码时,像 sorted、filter、map 这类”只吃一个闭包”的方法,几乎总是用这种省略写法。
1-3 map 的经典例子
尾随闭包最适合展示的地方,是 Array 的 map(_:) 方法。它接收一个闭包,对数组里每个元素调用一次,把每次返回的新值收集成一个新数组。map 的闭包往往不止一行,所以尾随闭包正好派上用场。
下面把一组整数转成对应的中文数字串:
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]
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 为 ["OneSix", "FiveEight", "FiveOneZero"]
闭包体里有循环、有变量、有 return,如果塞进 map(_:) 的小括号里会非常难看。写成尾随闭包后,逻辑清清爽爽地跟在函数后面。map 每处理一个元素就调用一次闭包,闭包内部把 number 当成可变变量用(闭包参数本身是不可改的常量,所以这里重新声明了一个同名 var)。
Tip新手常犯的错:把
(number) -> String in写进小括号里当普通参数。记住,一旦用尾随闭包,闭包整体就跑到函数括号外面了,不要再在函数里写参数标签。
1-4 多个尾随闭包
如果一个函数接收不止一个闭包参数,你可以写多个尾随闭包。规则是:第一个尾随闭包省略参数标签,后面的每个尾随闭包都要带上自己的参数标签。
设想一个加载图片的函数,它有两个闭包,一个成功回调,一个失败回调:
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
if let picture = download("photo.jpg", from: server) {
completion(picture)
} else {
onFailure()
}
}
调用时,第一个闭包省标签,第二个闭包带上 onFailure: 标签:
loadPicture(from: someServer) { picture in
someView.currentPicture = picture
} onFailure: {
print("Couldn't download the next picture.")
}
这种”第一个不写标签、其余都写标签”的规则,是多个尾随闭包的固定写法。它让成功和失败两段逻辑在视觉上分得很开,比挤在一个大闭包里用 if/else 判断要清楚得多。
Note多个尾随闭包是从 Swift 5.3 起稳定支持的语法。它本质上只是语法糖,编译后和把闭包一个个传进小括号没有任何区别。
1-5 尾随闭包的可读性权衡
尾随闭包虽好,也不是无脑把闭包都挪出去。当闭包特别长、逻辑很重时,虽然挪到括号外透气了,但函数名和真正的主语被一段大括号隔远了,读的人要往下翻很久才看到闭包在干嘛。这种时候,如果闭包逻辑确实复杂,把它抽成一个有名字的嵌套函数或独立函数,再当普通参数传进去,反而更清楚。尾随闭包解决的是”短闭包更好读”,不是”所有闭包都该拖在后面”。
还有一个容易被忽略的点:尾随闭包会改变代码的”视觉主角”。平常你读 someFunction(arg1, arg2) 时,注意力在函数名和参数;用了尾随闭包后,函数名后面跟着一整块 {},这块闭包反而成了视觉重心。在 Swift 标准库里,像动画、网络回调这类”主角是那段逻辑”的 API 大量使用尾随闭包,读起来顺;但如果一个函数既有正常参数又有尾随闭包,而闭包又不长,保留在小括号里反而和参数列表更成一体。
新手还有个常见误解:以为尾随闭包是某种”高级闭包”,和普通闭包不一样。其实它和写在括号里的闭包完全等价,编译后没有任何区别,纯粹是书写位置的语法糖。理解了”它只是把闭包从参数列表挪到外面”,就不会被各种花式写法吓到。你看到的 view.animate { ... }、data.forEach { ... },本质上都和 view.animate(completion: { ... }) 是一回事。
1-6 易错点速记
记住三条就够用:第一,尾随闭包只能用于最后一个参数;第二,当它是唯一参数时,函数名后的空括号可省,写成 method { ... };第三,多个尾随闭包时,只有第一个省略标签,其余必须写上标签。把”函数名加闭包块”当成整体来读,很多花式 API 就不吓人了。如果你看到某个调用既不写闭包标签、括号里又空空如也,那八成是尾随闭包在起作用。
1-7 小结
尾随闭包的全部意义就是”把长闭包挪到函数括号外面,让代码透气”。记住三条:闭包得是最后一个参数;唯一参数时可以连空括号都省;多个尾随闭包时第一个不写标签、其余写标签。下一章我们深入闭包最有意思的机制——值捕获。