Swift语言学习(六)

原文链接:http://www.ioswift.org/

11.闭包

闭包是可以在代码中传递和使用的自包含的功能模块。Swift 的闭包是类似于 C 、 Objective - C 的 block 、其他编程语言的lambdas。
闭包可以捕获和存储上下文任何引用常量和变量,也就是关闭这些常量和变量,因此得名“闭包”。Swift 管理所有闭包的捕获和内存操作。

注意:如果不熟悉捕获(capturing)概念不用担心,可以在值捕获章节详细了解。


全局函数和嵌套函数,实际上是特殊情况的闭包。闭包有三种形式:

     1.全局函数是一个有名字但不会捕获任何值的闭包
     2.嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
     3.闭包表达式是一个通过轻量级语法定义的可以捕获其上下文中变量或常量值的匿名闭包
 
Swift 的闭包表达式是简洁的、清晰的。并鼓励使用简短整齐的语法,主要优化如下:
     1.通过上下文推断参数和返回值类型
     2.隐式返回单表达式闭包
     3.速记参数名称
     4.尾随(Trailing)闭包语法

11.1.闭包表达式

嵌套函数:在一个复杂函数中独立的命名和定义一部分代码块。有时候没有完整定义和命名的类函数结构是很有用处的,比如,在处理将其他函数作为一个函数参数的时候。

闭包表达式是一种通过简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使闭包的写法简洁明了。

来看一组例子:通过 sort 函数闭包的定义和语法优化,展示相同的功能下的每一次优化的闭包实现。

11.2.Sort函数

Swift 标准库提供了sort 函数。sort 函数主要是对同一类型的数组值进行排序,如何排序是基于使用者提供的排序闭包。当排序完成的时候,会返回一个与原数组有着相同大小和类型的已经排序的新数组。
例如:对 String 类型的数组进行逆序排序,初始数组如下:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

sort 函数需要传入两个参数:
    1. 已知类型的数组;
    2. 闭包函数。需要传入与数组元素类型相同的两个参数值,并返回一个布尔值来告诉 sort 函数传入的两个参数的顺序:如果第一个参数值在第二个参数值之前,返回true,否则返回 false。

该例子对 String 类型的数组进行排序,所以闭包函数类型是: (String, String) -> Bool

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

如果 s1 > s2 ,backwards函数返回true,在新的数组中 s1 会在 s2 前。 对于字符串中的字符来说,“大于” 表示 “按照字母顺序出现较晚”。 比如字母"B"大于字母"A",字符串 "Tom"大于字符串 "Tim" 。 这里是字母逆序排序,"Barry" 将会排在 "Alex" 之后。

这是一个比较冗长的写法。本质上我们只需要一个单表达式 (a > b),我们可以进一步进行优化。

11.3.闭包表达式语法

闭包表达式语法:

{ ( parameters ) ->  return type  in
     statements
}

闭包表达式语法可以使用常量、变量和 inout 类型作为参数,不提供默认值。 也可以在参数列表的最后使用可变参数。 元组也可以作为参数和返回值。

下面的例子展示了之前 backwards 函数对应的闭包表达式版本的代码:

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

内联闭包参数和返回值类型与 backwards 函数类型相同:(s1: String, s2: String) -> Bool 。在内联闭包中,函数和返回值类型都写在大括号内,而不是大括号外。

内联闭包的函数体部分由关键字 in 引入。 该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
这个闭包的函数体部分可以写成一行代码:

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

11.4.根据上下文推断类型

因为 sort 函数的第二个参数是闭包,所以 Swift 可以推断闭包的参数和返回值类型。
sort 函数期望第二个参数类型是: (String, String) -> Bool 类型的闭包。所以此闭包并不需要声明其参数和返回值类型,那么箭头 (->) 和围绕在参数周围的括号也可以被省略:

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

因为 Swift 可以推断出闭包的参数和返回值类型,所以不需要完整格式构造内联闭包。

11.5.单表达式闭包隐式返回

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

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

sort 函数的闭包参数(第二个参数)明确了必须返回一个 Bool 类型值。

因为闭包函数体只包含了一个单一表达式 (s1 > s2),该表达式返回 Bool 类型值,所以 return 关键字可以省略。

11.6.参数名称缩写

Swift 为内联函数提供了参数名称缩写功能,通过 $0,$1,$2 顺序使用闭包的参数。

如果使用了参数缩写名称,在函数参数列表中可以省略对闭包的定义,Swift 也会推断闭包参数缩写名称的类型。 in 关键字同样被省略。
此时闭包表达式完全由闭包函数体构成:

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

在这个例子中,$0 和 $1 表示闭包中第一个和第二个 String 类型的参数

11.7.运算符函数

上面例子中的闭包表达式还有一种更简短的写法。
Swift 的 String 类型定义了大于号 (>) 的字符串实现(运算符重载):作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。这正好符合 sort 函数的第二个参数类型。 因此,可以简单地只传递一个大于号,Swift 可以自动推断出字符串的大于号函数实现:

reversed = sort(names, >)

关于运算符表达式的更多内容请查看 运算符函数 一章。

11.8.尾随闭包(Trailing Closures)

如果作为函数最后一个参数的闭包表达式比较长,可以使用尾随闭包来增强函数的可读性。
尾随闭包是一个在函数被调用时,写在函数括号之后的闭包表达式。
func someFunctionThatTakesAClosure(closure: () -> ()) {
    // function body goes here
}
 
// here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure({
    // closure's body goes here
    })
 
// here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
    // trailing closure's body goes here
}
注意:如果函数的参数只有一个闭包表达式,当使用尾随闭包时,可以把()省略掉。

在上例中 sort 函数的字符串排序闭包可以改写为:
reversed = sort(names) { $0 > $1 }

当 闭包代码很长,不能在一行代码中写完时,尾随闭包会非常有用。 比如,Swift 的 Array 类型有一个 map 方法,它使用闭包表达式作为其唯一参数。 数组中的每一个元素都会调用一次该闭包,并返回该元素对应的值(也可以是不同类型的值)。 具体的映射方式和返回值类型由闭包来指定。
数组使用闭包函数后,map 方法将返回一个新的数组,数组中包含了原数组中元素映射后的值。
例子:在 map 方法中使用尾随闭包将 Int 类型数组 [16,58,510] 转换为包含对应 String 类型的数组 ["OneSix", "FiveEight", "FiveOneZero"]:
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]
digitNames 是一个数字和名字映射的字典。 numbers 是整型数组,我们要将它转换成字符串数组。
通过传递一个尾随闭包给 numbers 的 map 方法来获取对应的字符串数组。 需要注意的是调用 numbers.map 不需要在 map 后面包含任何括号,因为它只需要传递一个闭包表达式参数:
let strings = numbers.map {
    (var number) -> String in
    var output = ""
    while number > 0 {
        output = digitNames[number % 10]!  + output
        number /= 10
    }
    return output
}
// strings is inferred to be of type String[]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
map 为数组中的每一个元素调用了闭包表达式。 并且无需指定闭包的输入参数 number 的类型,Swift 会进行类型推断。
闭包 number 参数被声明为一个变量参数 ,因此可以在闭包函数体内对其进行修改。闭包的返回值类型为 String,因为新数组的元素类型为 String。
闭包在每次被调用的时候都创建了一个字符串并返回。 使用求余运算符 (number % 10) 计算最后一位数字,digitNames 字典根据数字值获取对应字符串。

注意:字典 digitNames 下标后跟着一个叹号 (!),因为字典下标返回一个可选值 (optional value),表明即使该 key 不存在也不会出现查找失败。

11.9.捕获值

闭包可以捕获在上下文中定义过的常量或变量。 即使这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift 最简单的闭包形式是嵌套函数,也就是定义在其他函数体内的函数。 内部(嵌套)函数可以捕获外部函数所有的参数以及常量和变量。


例子: makeIncrementor 函数包含了 incrementor 函数。 嵌套函数 incrementor 从上下文中捕获了两个值,runningTotal 和 amount。 之后 makeIncrementor 将 incrementor 作为闭包返回。 每次调用 incrementor 时,runningTotal 都会增加一次 amount 值。

func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}
makeIncrementor 返回类型是 () -> Int , 说明其返回的是一个函数。
makeIncrementor 函数定义了一个整型变量 runningTotal(初始为0) 用来存储总数。 该值通过 incrementor 函数返回。
makeIncrementor 有一个 Int 类型的参数,其外部命名为 forIncrement, 内部命名为 amount。表示每次 incrementor 被调用时 runningTotal 将要增加的量。
incrementor 函数用来执行增加的操作。 runningTotal += amount ,并将其返回。

单独来看 incrementor 函数:

func incrementor() -> Int {
    runningTotal += amount
    return runningTotal
}
incrementor 函数没有参数,但是在函数体内访问了 runningTotal 和 amount 变量。每次调用该函数的时候都会修改 runningTotal 的值,incrementor 捕获了当前 runningTotal 变量的引用,保证了当 makeIncrementor 结束时候 runningTotal 的值会继续增加。
注意:Swift 会决定捕获引用还是拷贝值。Swift 会管理 runingTotal 变量的内存,如果不再被 incrementor 函数使用,会被释放。

调用 makeIncrementor :

let incrementByTen = makeIncrementor(forIncrement: 10)
该例子定义了一个指向 incrementor 函数的 incrementByTen 的常量(函数类型)。 调用 incrementByTen 多次可以得到以下结果:
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30

如果您创建了另一个 incrementor 函数,它会有自己独立的 runningTotal 变量的引用。 下面的例子中 incrementBySevne 捕获了一个新的 runningTotal 变量,该变量和 incrementByTen 中捕获的变量没有任何关系:

let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
// returns a value of 7
incrementByTen()
// returns a value of 40

11.10.闭包作为引用类型

上面的例子中,incrementBySeven 和 incrementByTen 是常量,但是它们指向的闭包仍然可以增加其捕获的变量值。这是因为函数和闭包都是引用类型。
无论将闭包/函数赋值给一个常量或变量,实际上都是指向闭包/函数的引用。 incrementByTen 是指向闭包引用的一个常量。

如果将闭包/函数赋值给了两个不同的常量或变量,它们指向同一个闭包/函数:

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

猜你喜欢

转载自usench.iteye.com/blog/2272276