[关闭]
@phper 2016-09-11T16:19:25.000000Z 字数 2589 阅读 4651

swift 9. 闭包

swift


闭包在一般的语言中也有,而且用法也差不多,swift中也是有闭包的概念的,这一节来学习swift中的闭包。

什么是闭包

闭包(closure),闭包在其它语言中也叫匿名函数,lambda等,实际上就一个去掉了函数名的函数体,它可以捕获和存储其所在上下文中任意常量和变量的引用。 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。

swift中有个sort函数,可以来给数组按照自定义的函数排序。上一节讲函数的时候,也说到这个函数,我用这个例子来先说明一下闭包的概念。

  1. //生成一个0-99的数组
  2. var arr:[Int] = []
  3. for i in 0..<100 {
  4. arr.append(i)
  5. }
  6. // 从大到小
  7. func sortDesc(a:Int, b:Int) -> Bool{
  8. return a > b
  9. }
  10. // 从小到大
  11. func sortAsc(a:Int, b:Int) -> Bool{
  12. return a < b
  13. }
  14. //普通方式
  15. arr.sort(sortDesc)
  16. arr.sort(sortAsc)
  17. //闭包方式
  18. arr.sort({ (a:Int, b:Int) -> Bool in
  19. return a > b
  20. })
  21. arr.sort({ (a:Int, b:Int) -> Bool in
  22. return a > b
  23. })

闭包表达式语法

根据上面的一个简单的例子,我们知道来一个闭包实质上也是一个函数,所以我们总结和精简一下,如何定义一个闭包:

闭包一般是这样定义的:

{ (parameters) -> returnType in
    //函数体
    statements
} 

闭包对比函数来说,多了一个{}in关键字。
闭包表达式语法可以使用常量变量inout类型作为参数,不提供默认值。 也可以在参数列表的最后使用可变参数。 元组也可以作为参数和返回值。

简化的写,我们也可以写成一行,这样更简洁看起来:

arr.sort({ (a:Int, b:Int) -> Bool in return a > b })

根据上下文推断类型

上面的例子中sort函数,参数是类型为(Int, Int) -> Bool的函数,因此实际Int,Int和Bool类型并不需要作为闭包表达式定义中的一部分。 因为所有的类型都可以被正确推断,返回箭头 (->) 和围绕在参数周围的括号也可以被省略:

reversed = arr.sorted({ a, b in return a > b } )

看,是不是简洁多了。

单表达式闭包隐式返回

单行表达式闭包可以通过隐藏return关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:

reversed = arr.sorted({ a, b in a > b } )

我擦,更加简洁了有没有???

参数名称缩写

Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数。

如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。 in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

reversed = arr.sort({ $0 > $1 } )

运算符函数

在上一节函数讲到高阶函数reduce时候,直接用>来表示一个函数

array2.reduce(0, combine: >)。

闭包中,也可以这样使用:用 >、< 来做运算,更加简洁:

arr.sort(>)
arr.sort(<)

尾随闭包 (Trailing-Closure)

如果将一个很长的闭包表达式作为一个参数参数一个函数,这样可读性就会很差,而且写起来很蛋疼。所以,就出现了尾随闭包,尾随闭包就是讲{}搬到()的外面去,这样看起来可读性更高。

  1. // 以下是不使用尾随闭包进行函数调用
  2. someFunctionThatTakesAClosure({
  3. // 闭包主体部分
  4. })
  5. // 以下是使用尾随闭包进行函数调用
  6. someFunctionThatTakesAClosure() {
  7. // 闭包主体部分
  8. }

所以刚才的那个例子,也能这样写:

  1. reversed = sorted(names) {s1, s2 in return s1 > s2}

当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。

来一个尾随闭包原始的写法:

  1. arr.sort() {
  2. a, b in return a > b
  3. }

甚至,我们可以将()也可以去掉:

  1. arr.sort{
  2. a, b in return a < b
  3. }

捕获值

闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

还是sort这个例子:

  1. // 数值捕获
  2. var num = 300
  3. arr.sort{ a , b in
  4. abs(a-num) < abs(b-num)
  5. }

我们在闭包中并没有神马这个num值,那么在闭包中,是可以轻易捕获到的。

闭包是引用类型

在swift中函数和闭包都是引用类型。

无论将函数/闭包赋值给一个常量还是变量,实际上都是将常量/变量的值设置为对应函数/闭包的引用。 值都会同事被改变。

看一个例子,定义了一个函数,参数是一天跑多少米,返回值是一个无参数,返回值是Int型的函数。可以看的出,返回值是一个{},即是闭包。

  1. //每天跑多少米,求总的跑数。
  2. func runningMetersWithMetersPerDay( metersPerDay: Int) -> () -> Int {
  3. var totalMeters = 0
  4. return {
  5. totalMeters += metersPerDay
  6. return totalMeters
  7. }
  8. }
  9. var p1 = runningMetersWithMetersPerDay(1000)
  10. p1() //1000
  11. p1() //2000

多次调用,发现数字在累加,而不是重新开始,说明闭包是个引用赋值的语句。

这个时候,p1是一个无参数,返回值是Int型的函数,多次调用,其实就是在调用

{
    totalMeters += metersPerDay
    return totalMeters
}

这部分。totalMeters 每次都会发生变化,即是引用赋值了,而 metersPerDay 每次都会捕获 传入的参数1000来的。

我们继续什么一个p2 ,每天拍3000米:

  1. var p2 = runningMetersWithMetersPerDay(3000)
  2. p2() //3000
  3. p2() //6000
  4. var p3 = p2 //引用赋值。
  5. p3() // 9000

可以看出。p3() 是接着累加的。进一步说明是闭包是引用赋值。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注