概述
闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用。闭包符合下列情况中的一种:
- 全局函数是一个有名字但不会捕获任何值的闭包;
- 内嵌函数是一个有名字且能从其上层函数捕获值的闭包;
- 闭包表达式是一个轻量级语法所写的可以捕获其上下文中常量或变量值的没有名字的闭包。
闭包表达式
闭包表达式可以近似理解为其他语言的 Lambda 表达式,语法为:
{ (parameters) -> (return type) in
statements
}
举个实际的例子,根据指定的规则对数组names
进行排序并返回新数组:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
受益于类型推导,上面的代码可以简化为:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })
通过$0
、$1
、$2
等名称可以引用闭包的实际参数值,因此上面的代码还可以简化为:
reversedNames = name.sorted(by: { $0 > $1 })
还有一种更简单的写法:
reversedNames = name.sorted(by: >)
尾随闭包
如果闭包的函数体很长,可以将其放在()
的外面,所以上面的例子可以简写为:
reversedNames = names.sorted() { $0 > $1 }
如果闭包表达式是函数唯一的实际参数,又使用了尾随闭包的写法,就可以省略()
:
reversedNames = names.sorted { $0 > $1 }
捕获值
一个闭包能够从上下文捕获已被定义的常量和变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
forIncrement() // 10
incrementer
函数没有定义参数,但可以捕获上级函数的runningTotal
和amount
参数,并且这两个参数在不被需要后才会自动释放。
函数和闭包都是引用类型,所以将一个闭包赋值到两个不同的常量或变量中时,这两个常量或变量都指向相同的闭包。
逃逸闭包
当闭包作为参数传递给一个函数时,就称这个闭包逃逸了,因为它是在函数返回之后调用的。在声明一个接受闭包作为参数的函数时,可以在形参前写@escaping
来明确闭包是允许逃逸的。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
自动闭包
自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包。它不接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值。
自动闭包允许你延迟处理,因此闭包内部的代码直到你调用它的时候才会运行。对于有副作用或者占用资源的代码来说很有用,因为它可以允许你控制代码何时才进行求值。
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let customerProvider = { customersInLine.remove(at: 0) }
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
@autoclosure
表示参数是自动闭包。