//闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单 明了。下面闭包表达式的例子通过使用几次迭代展示了 sort(_:) 方法定义和语法优化的方式。每一次迭代都用更 简洁的方式描述了相同的功能。
//sort 方法(The Sort Method)
//Swift 标准库提供了名为 sort 的方法,会根据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。一 旦排序完成, sort(_:) 方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组 不会被 sort(_:) 方法修改。
//下面的闭包表达式示例使用 sort(_:) 方法对一个 String 类型的数组进行字母逆序排序.以下是初始数组值:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
//sort(_:) 方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明 当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排 序闭包函数需要返回 true ,反之返回 false 。
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sort(backwards)
print(reversed)
//如果第一个字符串( s1 )大于第二个字符串( s2 ), backwards(_:_:) 函数返回 true ,表示在新的数组中 应该出现在 s2 前。对于字符串中的字符来说,“大于”表示“按照字母顺序较晚出现”。这意味着字母 "B" 大 于字母 "A" ,字符串 "Tom" 大于字符串 "Tim" 。该闭包将进行字母逆序排序, "Barry" 将会排在 "Alex" 之前。
//然而,这是一个相当冗长的方式,本质上只是写了一个单表达式函数 ( a > b )。在下面的例子中,利用闭合表达 式语法可以更好地构造一个内联排序闭包。
//下面的例子展示了之前 backwards(_:_:) 函数对应的闭包表达式版本的代码:
reversed = names.sort({ (s1: String, s2: String) -> Bool in
return s1 > s2
})
print(reversed)
//由于这个闭包的函数体部分如此短,以至于可以将其改写成一行代码:
reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } )
//根据上下文推断类型(Inferring Type From Context)
//因为排序闭包函数是作为 sort(_:) 方法的参数传入的,Swift 可以推断其参数和返回值的类型。 sort(_:) 方法 被一个字符串数组调用,因此其参数必须是 (String, String) -> Bool 类型的函数。这意味着 (String, Strin
//g) 和 Bool 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头( -> )和 围绕在参数周围的括号也可以被省略:
reversed = names.sort( { s1, s2 in return s1 > s2 } )
//单表达式闭包隐式返回(Implicit Return From Single-Expression Clossures
//单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
reversed = names.sort( { s1, s2 in s1 > s2 } )
//参数名称缩写(Shorthand Argument Names)
//Swift 自动为内联闭包提供了参数名称缩写功能,您可以直接通过 $0 , $1 , $2 来顺序调用闭包的参数,以此 类推。
//如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的 类型会通过函数类型进行推断。 in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
reversed = names.sort( { $0 > $1 } )
//在这个例子中, $0 和 $1 表示闭包中第一个和第二个 String 类型的参数。
//运算符函数(Operator Functions)
//实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。Swift 的 String 类型定义了关于大于
//号( > )的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。而这正好与 sor t(_:) 方法的第二个参数需要的函数类型相符合。因此,您可以简单地传递一个大于号,Swift 可以自动推断出您 想使用大于号的字符串函数实现:
reversed = names.sort(>)
/*--------------尾随闭包(Trailing Closures)-----------*/
func someFunctionThatTakesAClosure(closure: () -> Void) { // 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure({
// 闭包主体部分
}
)
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
//作为 sort(_:) 方法参数的字符串排序闭包可以改写为:
reversed = names.sort() { $0 > $1 }
//如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把 () 省略掉:
reversed = names.sort { $0 > $1 }
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 {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10 }
return output
}
print(strings)
// strings 常量被推断为字符串类型数组,即 [String] // 其值为 ["OneSix", "FiveEight", "FiveOneZero"]
/*--------------捕获值(Capturing Values)-----------*/
//闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
//Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可 以捕获其外部函数所有的参数以及定义的常量和变量。
//
//举个例子,这有一个叫做 makeIncrementor 的函数,其包含了一个叫做 incrementor 的嵌套函数。嵌套函数 incre mentor() 从上下文中捕获了两个值, runningTotal 和 amount 。捕获这些值之后, makeIncrementor 将 increment or 作为闭包返回。每次调用 incrementor 时,其会以 amount 作为增量增加 runningTotal 的值
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
//下面是一个使用的例子:
let incrementByTen = makeIncrementor(forIncrement: 10)
print(incrementByTen()) // 返回的值为10
print(incrementByTen()) // 返回的值为20
print(incrementByTen()) // 返回的值为30
//如果您创建了另一个 incrementor ,它会有属于它自己的一个全新、独立的 runningTotal 变量的引用:
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
// 返回的值为7
//再次调用原来的 incrementByTen 会在原来的变量 runningTotal 上继续增加值,该变量和 incrementBySeven 中捕 获的变量没有任何联系:
print(incrementByTen()) // 返回的值为40
//闭包是引用类型(Closures Are Reference Types)
let alsoIncrementByTen = incrementByTen
print(alsoIncrementByTen())
// 返回的值为50
/*--------------非逃逸闭包(Nonescaping Closures)-----------*/
//当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当 你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @noescape ,用来指明这个闭包是不允许“逃 逸”出这个函数的。将闭包标注 @noescape 能使编译器知道这个闭包的生命周期(译者注:闭包只能在函数体中 被执行,不能脱离函数体执行,所以编译器明确知道运行时的上下文),从而可以进行一些比较激进的优化
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
closure()
}
//举个例子, sort(_:) 方法接受一个用来进行元素比较的闭包作为参数。这个参数被标注了 @noescape ,因为它确 保自己在排序结束之后就没用了。
//一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异 步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包 直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调 用。例如:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandlers.append(completionHandler)
}
//someFunctionWithEscapingClosure(_:) 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组 中。如果你试图将这个参数标注为 @noescape ,你将会获得一个编译错误。
//将闭包标注为 @noescape 使你能在闭包中隐式地引用 self
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNoescapeClosure { x = 200 }
} }
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// prints "200"
completionHandlers.first?()
print(instance.x)
// prints "100"