闭包表达式语法
本教程共 93 篇 · 第 34 篇 · 更新于 2026-08-08 · 约 8 分钟阅读
本节目标:掌握 Swift 闭包表达式的完整写法,以及编译器帮你做掉的那些简化,能看懂从完整到极简的每一种形态。
很多初学者一看到闭包就头皮发麻,其实它没那么玄。你可以把闭包理解成”一段没有名字、可以随身带着走的函数”。函数能干的事,闭包基本都能干;区别只是闭包没有名字,而且写起来更轻巧。Swift 里的全局函数、嵌套函数,本质上都是闭包的特殊形态,只是它们有名字、或者能捕获外层变量。
本章只讲”闭包表达式”这一种写法——也就是用大括号 {} 现场写一段可传递的代码块。后面几章会讲尾随闭包、值捕获、逃逸闭包,那都是建立在今天这个地基上的。
1-1 为什么需要闭包表达式
设想一个场景:你有一个字符串数组,想把里面的名字按倒序排好。Swift 标准库提供了一个 sorted(by:) 方法,它接收一个”比较闭包”,根据你给的规则把数组排好,返回一个新的排好序的数组。原数组不会被改动,这一点要记牢。
最简单的做法,是先写一个普通函数,再把它传进去:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
函数 backward 的逻辑其实就一句话:s1 > s2。为了这一句话,你却要起名字、写类型、写 return,显得很啰嗦。闭包表达式就是用来消灭这份啰嗦的——它让你把逻辑直接写在使用现场,不必先去别处定义一个有名字的函数。
1-2 闭包表达式的完整语法
闭包表达式长这样:
{ (参数列表) -> 返回类型 in
语句
}
注意三个要点。第一,整段代码包在一对大括号里。第二,参数和返回类型写在大括号内部的最前面,而且参数可以有名字和类型标注,比如 (s1: String, s2: String) -> Bool。第三,有一个特殊的 in 关键字,它像一道分界线:in 前面是”参数和返回类型的声明”,in 后面才是”闭包真正要执行的代码”。
把上面那个 backward 函数改写成闭包表达式,是这样的:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
你会发现,参数声明部分和函数几乎一模一样,只是位置搬进了大括号里,并且用 in 把”声明”和”实现”隔开。因为闭包体很短,它甚至可以挤成一行:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
Note闭包表达式的参数可以是
in-out参数,但不能有默认值。可变参数只有在你给它起名字时才能用。元组和一般类型一样,既能当参数类型也能当返回类型。
1-3 让编译器帮你推断类型
闭包是作为函数的参数传进去的,编译器往往已经知道它该长什么样。比如 sorted(by:) 作用在字符串数组上,它需要的闭包类型一定是 (String, String) -> Bool。既然类型板上钉钉,你就可以把参数类型和返回类型都省掉:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })
类型推断永远成立的前提是:闭包确实作为函数或方法的参数被传入。所以当你把闭包直接当参数用时,几乎从不需要写完整形态。当然,如果你想写清楚类型让代码更好读,写出来也完全没问题,这不是错误,只是不是必须。
1-4 单表达式闭包的隐式返回
如果闭包体里只有一条表达式,而且这条表达式的返回值类型刚好对得上,那连 return 关键字都能省。Swift 会自动把这条表达式的结果当作返回值:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })
这里 s1 > s2 本身就返回 Bool,正好符合 sorted(by:) 的要求,于是 return 可以省略。初学者常在这里纠结”为什么没有 return 也有返回值”,记住这条规则就行:单表达式闭包,省 return 是常态。
1-5 速记参数名 $0、$1
更狠的简化来了。Swift 会给闭包的参数自动起一套速记名字:$0 代表第一个参数,$1 代表第二个,以此类推。一旦你在闭包里用了这些速记名,参数列表整个都可以删掉,连 in 也一并省了——因为闭包从头到尾就只剩一句执行代码:
reversedNames = names.sorted(by: { $0 > $1 })
$0 和 $1 在这里就是两个 String。你用了几个速记名,编译器就知道闭包接收几个参数。这种写法在代码短小的时候特别常见,但也要小心:如果闭包逻辑稍长,满屏 $0 $1 反而让人看不懂,那种时候老老实实写参数名更好。
1-6 运算符方法:还能更短
你以为到 $0 > $1 就到头了?Swift 的 String 类型把大于号 > 实现成了一个方法,它正好接收两个 String、返回 Bool,和 sorted(by:) 需要的闭包类型一模一样。于是连闭包都不用写,直接把运算符当参数传进去就行:
reversedNames = names.sorted(by: >)
这一行和最初那个 backward 函数完成的是同一件事,只是写法从六七行压缩到了一行。从完整形态一路简化到这里的全过程,你心里要有一条清晰的线:先省类型、再省 return、再省参数名、最后用运算符方法。每一层简化都不是魔法,都是编译器替你做了本可以省略的推断。
Tip简化是把双刃剑。读别人的 Swift 代码时,你一定会遇到
sorted(by: >)这种极简写法;但自己写的时候,如果闭包逻辑超过一行,优先保证可读性,别为了炫技一路省到$0。
1-7 闭包是引用类型
这一点放到本章末尾提一句,因为后面讲值捕获时要用到它。当你把一个闭包赋给常量或变量时,你存的是”指向那段代码的引用”,而不是把代码复制了一份。看下面这个自包含的例子,两个常量指向的是同一个闭包:
func makeCounter() -> () -> Int {
var count = 0
return { count += 1; return count }
}
let counterA = makeCounter()
let counterB = counterA // 两个常量指向同一个闭包
counterA() // 返回 1
counterB() // 返回 2
调用哪个,效果都一样,因为它们背后是同一段闭包。这条性质在嵌套函数捕获变量时尤其重要,下一章讲尾随闭包、下下章讲值捕获时你会反复用到它。
1-8 小结
闭包表达式的核心只有一句:大括号包起来,参数和返回类型写在前,in 后面是真正的代码。编译器会替你推断类型、替单表达式闭包隐式返回、还给你准备好 $0 $1 速记名。简化是循序渐进的,不是非得一步到位。下一章我们看尾随闭包,那是闭包表达式最常用的”落地点”。