六、闭包 Closures

1. 概述

闭包是一种自包含的代码块(self-contained blocks),它实现某一特定功能,并可以在代码中进行传递和使用。闭包类似于 Objective-C 中的 block。

函数其实是一种特殊的闭包,闭包有以下三种形式:

  1)全局函数 Global functions —— 有名字,不捕获任何值

  2)嵌套函数  Nested functions —— 有名字,捕获自己作用域里的值 capture values from their enclosing function

  3)闭包表达式 Closure expressions —— 没有名字,捕获自己作用域里的值

2. 闭包表达式 Closure Expression

2.1. 引例

我们使用Swift库的排序函数 sorted对一组字符串进行排序:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] //待排序数组

func backwards(s1: String, s2: String)
-> Bool { // 比较器 return s1 > s2 }
var reversed = sort(names, backwards) // reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

上面的代码使用了函数的形式作为闭包定义比较器,它的类型是 (String, String) -> Bool。

显然,仅仅为了 s1>s2 写一个函数是很不简洁的。

 

2.2. 闭包表达式语法 Closure Expression Synta

闭包表达式的参数可以是constant、variable和inout,但不能是默认值参数(有默认值的参数)。元组 tuple 可以作为闭包表达式的参数和返回值

使用闭包表达式表示引例中的代码:

var reversed = sorted(names, {(s1: String, s2: String) -> Bool in 
          return s1 > s2      
    })

我们注意到,上面的内联闭包 inline closure 的参数和返回值与引例中的 backwards 函数是相同的,他们都是 (s1: String, s2: String) -> Bool,不同的是,内联闭包的参数和返值是写在大挂号里面,而不是外面。

in 关键字表示了闭包参数和返回值的结束,闭包体 closure’s body 的开始。

因为闭包体很短,所以也可以这样写:

var reversed = sorted(names, {(s1: String, s2: String) -> Bool in return s1 > s2})

 

2.3. 类型推断 Inferring Type From Contex

由于Swift的类型推断(sort 函数的第二个参数期望类型 expected type 是 (String, String) -> Bool,所以可以进行类型推断 ),我们可以省略sort 第二个参数的类型

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

 

2.4. 单表达式闭包省略return关键字 Implicit Returns from Single-Expression Closure

上面的语句,甚至return都可以不写

var reversed = sorted(names, {s1, s2 in s1 > s2})

因为闭包体是 (s1 > s2) ,它返回布尔型,所以省略return也不会引起混淆。

 

2.5. 速记参数名 Shorthand Argument Name

Swift为内联闭包提供了参数的速记方法,使用 $0, $1, $2 等来速记闭包中的参数值。如果你在闭包中使用速记参数名,就可以省略闭包中的参数表。速记参数名的类型将通过参数的expected type 推导出来。因为仅仅只有闭包体了,所以 in 关键字也能省略。

var reversed = sorted(names, {$0 > $1})

这里,$0 代表闭包中的第一个参数,$1代表闭包中的第二个参数。

 

2.6. 操作符函数 Operator Functions

由于Swift中的String类型定义了特殊的操作符——大于操作符 (>) ,它实际上是 (String, String) -> Bool 类型的函数,所以可以将它直接传递给sort函数。

var reversed = sorted(names, >)

 

3. 尾部闭包 Trailing Closures

当把闭包作为函数最后一个参数时,如果这个闭包表达式很长,可以使用尾部闭包。

尾部闭包是写在函数的挂号外面的闭包。

func someFunctionThatTakesAClosure(closure: () -> ()) {
    // function body goes here
} // 不使用尾部闭包 someFunctionThatTakesAClosure({ // closure's body goes here }) // 使用尾部闭 someFunctionThatTakesAClosure() { // trailing closure's body goes here }

当闭包表达式作为函数的唯一参数,并且这个函数使用尾部闭包来表示时,调用函数的时候,可以不写函数后的挂号 “()”

var reversed = sort(names){
  $0 > $1
}

 

4. 闭包的作用域——值的捕获 Capturing Values

这里以嵌套函数值的捕获为例

4.1. 嵌套函数的值的捕获——即嵌套函数变量的作用域

嵌套函数是写在其他函数体内的函数,它可以使用外部函数(母函数)outer function 的所有参数,并且可以使用母函数中定义的变量和常量。例如:

func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}    

makeIncrementor 的返回值是一个函数,类型是 () -> Int。由于子函数 incrementor 没有改变参数 amount 的值,所以子函数使用的实际上是 amount 值的副本。子函数改变了runningTotal 的值,使用的是 runningTotal 的引用。决定是使用副本还是引用是有Swift编译器决定的。

使用 makeIncrementor 函数:

// 定义一个 () -> Int 类型的函数 incrementByTen 
let incrementByTen = makeIncrementor(forIncrement: 10) 

// 调用 incrementByTen 函数
incrementByTen() // returns a value of 10
incrementByTen() // returns a value of 20
incrementByTen() // returns a value of 30

如果你创建另一个由 makeIncrementor 创建的函数 incrementBySeven,这个函数会存储自己的变量和参数副本。比如在下面的例子中,incrementBySeven 会获得一个全新的runningTotal 的引用,这个引用与 incrementByTen 中的引用没有任何联系。

let incrementBySeven = makeIncrementor(forIncrement: 7)

incrementBySeven()
// returns a value of 7 incrementByTen() // returns a value of 40

 

5. 闭包是引用类型 Closures Are Reference Types

在上面的例子中,incrementByTen 和 incrementBySeven 都是let定义的常量,但是他们却可以改变 runningTotal 值,因为闭包是引用类型(在上面的例子中,我们可以理解为一个常指针 char * const p,p的指向不能变,p的指向的内容可以变)。无论什么时候你将一个函数/闭包赋值给一个变量/常量,实际上是将这个函数/闭包的引用赋值给了常量/变量。在上面的例子中,incrementByTen 的值是闭包的引用,而不是闭包的内容。例如

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // returns a value of 50

 

参考:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID94

posted @ 2015-01-14 13:58  action爱生活  阅读(213)  评论(0编辑  收藏  举报