闭包是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。 在Swift中的闭包与C、OC中的blocks和其它编程语言(如Python)中的lambdas类似。
闭包可以捕获和存储上下文中定义的的任何常量和变量的引用。这就是所谓的变量和变量的自封闭, 因此命名为”闭包“(“Closures)”)。Swift还会处理所有捕获的引用的内存管理。
Swift中的闭包有很多优化的地方:
- 根据上下文推断参数和返回值类型;
- 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略return);
- 可以使用简化参数名,如
0, 1(从0开始,表示第i个参数…); - 提供了尾随闭包语法(Trailing closure Syntax)。
闭包表达式
嵌套函数是非常强大的功能,在一个函数体内嵌套另一个函数。将函数作为参数和返回值也非常有用。这些都是一些特殊情况下的闭包。
闭包表达式是一种简短的、集中的语法。闭包表达式为了缩短代码以及优化代码的阅读性,提供了几种语法优化。这里使用数组的排序为大家展示闭包的优化。
Sort方法
// 函数做参数,排序
let names = ["阳君","937447974","a","b","c"]
func backwards(s1: String,_ s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sort(backwards)
从代码中可以看出,将函数作为参数传递对于代码的阅读性不是很好,这里就需要闭包表达式对其优化。
闭包语法
闭包表达式的结构图如下所示:
- parameters:闭包接受的参数;
- return type:闭包运行完毕的返回值;
- statements:闭包内的运行代码。
下面运用闭包表达式代替backwards函数对sort进行优化。
reversed = names.sort({ (s1: String,s2: String) -> Bool in return s1 > s2 })
当要运行的代码很少时,你也可以将它们写在一行。
reversed = names.sort( { (s1: String,s2: String) -> Bool in return s1 > s2 } )
通过上下文判断类型
在闭包中,我们不必写参数的类型和返回值的类型,闭包可以通过上下文自动判断参数类型和返回值类型。
reversed = names.sort( { s1,s2 in return s1 > s2 } )
从单一表达式隐藏Return
在闭包中,如果运行的内容很少只有一行,则不必写return,闭包会自动返回。
reversed = names.sort( { s1,s2 in s1 > s2 } )
速记参数名称
在闭包中,我们不必命名参数名称。闭包中的参数可使用$
去获得,第一个参数为$0
,第二个为$1
。
reversed = names.sort( { $0 > $1 } )
算子函数
当在闭包中,只有一个表达式,做操作。如在sort中,只有两个参数做比较操作。闭包支持只输入>或<做比较。
reversed = names.sort(>)
尾随闭包
如果函数需要一个闭包作为参数,且这个参数是最后一个参数。我们又不想在()内写太多代码,我们可以运用尾随闭包。尾随闭包意味着闭包可以放在函数参数列表外,也就是括号外。
var reversed = names.sort() { $0 > $1 }
当尾随闭包中的参数只有一个时,我们可以省略()。
reversed = names.sort { $0 > $1 }
捕获值
闭包可以根据上下文环境捕获到定义的常量和变量。闭包可以引用和修改这些捕获到的常量和变量,就算在原来的范围内定义为常量或者变量已经不再存在。在Swift中闭包的最简单形式是嵌套函数。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print("\(incrementByTen())") // prints "10"
print("\(incrementByTen())") // prints "20"
print("\(incrementByTen())") // prints "30"
上面的例子介绍了,有个函数makeIncrementer,在函数内有一个嵌套函数incremented。嵌套函数可以通过上下文使用它的外部值runningTotal和amount。
当你想声明另一个闭包类型时,可以像声明属性一样声明。
let incrementBySeven = makeIncrementer(forIncrement: 7)
print("\(incrementBySeven())") // prints "7"
print("\(incrementByTen())") // prints "40"
可以看出两个闭包的引用是完全独立工作的。
闭包是引用类型
运用属性执行的闭包后,我们可以用另一个属性去引用,对于两个属性来说,它们是完全相同的,指向同一个闭包。
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // prints "50"
Noescape关键字
@noescape主要用于解决“保留环”问题如下所示,当你调用someFunctionWithEscapingClosure函数时,使用全局属性,会使用了self,这样你会发现每次调用闭包时,都会使用捕获self,这样容易造成内存泄露的问题,而且闭包中的操作其实是一成不变的,没有必要每次都访问。
Swift鉴于这种情况,希望在闭包内不使用self,因此产生了@noescape关键字。将@noescape写入闭包名前。这样在编写闭包内代码时,无须使用self属性,也避免了保留环问题。
var completionHandlers: [() -> Void] = []
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) { closure() // completionHandlers.append(closure) //会报错,closure无法被保存 } func someFunctionWithEscapingClosure(completionHandler: () -> Void) { completionHandler() completionHandlers.append(completionHandler) } 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"
Autoclosures关键字
在闭包中,我们调用函数时,是将代码封装为一个闭包传递给函数。如下所示:
var customersInLine = ["Chris","Alex","Ewa","Barry","Daniella"]
func serveCustomer(customerProvider: () -> String) { print("Now serving \(customerProvider())!") } serveCustomer( { customersInLine.removeAtIndex(0) } ) // prints "Now serving Alex!"
此时我们会思考,能否让代码直接为参数传递过去,也就是不用{}包含代码。@autoclosure关键字可以帮助你完成这种机制。
// customersInLine is ["Ewa","Daniella"]
func serveCustomer2(@autoclosure customerProvider: () -> String) { print("Now serving \(customerProvider())!") } // 闭包作为参数 serveCustomer2(customersInLine.removeAtIndex(0)) // prints "Now serving Ewa!"
使用了@autoclosure默认也是使用@noescape,如果你只想使用autoclosure的特性,不想使用noescape的特性,你可以使用escape关键字,如下所示:
// customersInLine is ["Barry","Daniella"]
var customerProviders: [() -> String] = []
//autoclosure和escaping一起用
func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) { customerProviders.append(customerProvider) } // 添加闭包,并且闭包此时为参数 collectCustomerProviders(customersInLine.removeAtIndex(0)) collectCustomerProviders(customersInLine.removeAtIndex(0)) //循环使用闭包 for customerProvider in customerProviders { print("Now serving \(customerProvider())!") } // prints "Now serving Barry!" // prints "Now serving Daniella!"
其他
参考资料
The Swift Programming Language (Swift 2.1)
文档修改记录
时间 | 描述 |
---|---|
2015-10-28 | 根据 The Swift Programming Language (Swift 2.1)中的Closures总结 |