Swift闭包(Closures)
最后更新于:2022-04-01 14:34:07
闭包是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。 在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)
~~~
从代码中可以看出,将函数作为参数传递对于代码的阅读性不是很好,这里就需要闭包表达式对其优化。
### 闭包语法
闭包表达式的结构图如下所示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-01_574e9af8cd710.jpg "")
- 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", "Barry", "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)](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html)
### 文档修改记录
| 时间 | 描述 |
|-----|-----|
| 2015-10-28 | 根据 [The Swift Programming Language (Swift 2.1)](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html)中的Closures总结 |
版权所有:[http://blog.csdn.net/y550918116j](http://blog.csdn.net/y550918116j)