闭包(Closure) 是一种能够捕获和存储其所在上下文中的变量和常量的独立代码块。换句话说,闭包可以在定义时捕获其作用域内的变量,并在闭包的生命周期内使用和修改这些变量。
闭包语法
闭包表达式的语法一般为:
{ (参数列表) -> 返回类型 in
// 闭包的代码
}
参数函数(传入值)和返回类型(返回值),in分隔参数和闭包体。
闭包还通常配合 () -> Void 函数类型使用,例如:
func perform(action: () -> Void) {
action()
}
() -> Void 表示参数是一个函数类型,可以传入普通函数,也可以传入闭包:
func foo() { print("foo") }
perform(action: foo) // 普通函数
perform(action: { print("bar") }) // 闭包字面量
当返回闭包时,也会使用 () -> Void 函数类型:
func makeLogger() -> () -> Void {
return {
print("log")
}
}
闭包类型
1、全局函数(Global Functions)
全局函数是定义在全局范围内的函数,有名称,并且不会捕获任何值。
func add(a: Int, b: Int) -> Int {
return a + b
}
2、嵌套函数(Nested Functions)
定义在函数内部,可以捕获并存储外层函数中的变量。
func outer() -> () -> Void {
let message = "Hello"
func inner() {
print(message)
}
return inner
}
let closure = outer()
closure() // 输出 "Hello"
3、闭包表达式(Closure Expressions)
闭包表达式可以捕获上下文中的变量,适用于需要传递函数作为参数的场景。
let addClosure: (Int, Int) -> Int = { (a, b) in
return a + b
}
print(addClosure(2, 3)) // 输出 5
注:闭包表达式的参数括号是可选的。
上面的闭包表达式等价于:
func addClosure(a: Int, b: Int) -> Int {
return a + b
}
print(addClosure(2, 3)) // 输出: 5
闭包语法简化
Swift可以通过类型推断和尾随闭包,简化闭包写法:
let addClosure: (Int, Int) -> Int = { $0 + $1 }
$0 和 $1 是第一个、第二个参数的建成。
如果闭包是函数最后一个参数,可以使用尾随闭包:
func performTask(completion: () -> Void) {
completion()
}
performTask {
print("任务完成")
}
如果不使用尾随闭包,默认写法需要显式参数名称:
performTask(completion: {
print("任务完成")
})
闭包捕获变量
闭包可以捕获外部作用域中的变量:
func makeIncrementer(amount: Int) -> () -> Int {
var total = 0
return {
total += amount
return total
}
}
let incrementBy2 = makeIncrementer(amount: 2)
print(incrementBy2()) // 2
print(incrementBy2()) // 4
makeIncrementer方法在传入数字2后,返回一个闭包:
{
total += amount
return total
}
这个闭包被incrementBy2持有(强引用)。闭包捕获了makeIncrementer方法中的total变量。
注意:这里闭包捕获的不是变量的值,而是变量存储位置的引用。

可以理解为,原本total只是makeIncrementer方法中的属性,方法结束后,属性会被移除,但是返回的闭包捕获了total变量。
当方法结束后,makeIncrementer会在函数返回后被销毁,而totalb变量会因为被闭包捕获,从栈内存提升到堆内存,闭包持有对这块堆内存的引用。
只要闭包存活,total就会存活。每次调用闭包,都会操作同一个total。
这个闭包的生命周期由incrementBy2管理,当incrementBy2所在的视图/闭包被释放的时候,incrementBy2被销毁,对应的闭包也会被销毁。
或者,如果incrementBy2是可选类型:
@State private var incrementer: (() -> Int)?
incrementer = makeIncrementer(amount: 2)
当incrementBy2为nil时,强引用断开,闭包释放。
这部分的引用知识,深入理解请见《Swift ARC自动引用计数》和《Swift深入理解闭包捕获机制》。
逃逸闭包和非逃逸闭包
闭包默认为非逃逸(Non-escaping)闭包,只能在函数返回前调用。
func foo(block: () -> Void) {
block()
}
当闭包被异步执行或被变量保存时,就会变成逃逸闭包。
var savedClosure: (() -> Void)?
func saveClosure(block: @escaping () -> Void) {
savedClosure = block
}
需要添加 @escaping 修饰符,用来标记该闭包可能会逃出函数。
当访问实例成员时,需要显式添加 self. ,防止引用循环。
总结
Swift闭包可以作为参数传入,也可以作为返回值返回,还可以持有并修改作用域内的变量,即使作用域已经销毁,闭包仍然可以访问这些变量。
既可以捕获外部变量,也可以存储到变量(逃逸闭包)中。
相关文章
1、Swift异步
