the swift programming language 5Swift编程swift

第7章:闭包

2019-02-17  本文已影响2人  行知路

  闭包是自包含的功能块,可以在代码中传递和使用。Swift中的闭包类似于Objective-C中的块以及其他编程语言中的lambdas。闭包也类似于C语言中的函数指针,但是其比函数指针强大。闭包定义它们的上下文中捕获和存储对任何常量和变量的引用,Swift为您处理捕获的所有内存管理。
  闭包有三种形式:

7.1 闭包表达式

全局函数与嵌套函数在前面章节已经讲述过,下面以数组的sorted(by:)方法为例讲述闭包表达式。

通过提供函数来实现数组的排序,以下讲解中排序方法都是降序排列

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

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

  以下通过闭包表达式与上述通过函数调用的storted方法进行对比。

闭包表达式的一般语法

{ (parameters) -> return type in
    statements
}

完整闭包表达式排序

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

由于storted方法明确给出了参数的类型——即s1、s2两个变量的类型,所以参数部分可以类型部分可以交由Swift编译器进行推断,从而让我们省略参数类型

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

由于storted方法明确给出了闭包的返回类型——返回结果是Bool,所以闭包返回类型部分可以交由Swift编译器进行推断,从而让我们省略闭包返回类型

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

请注意只有在闭包是单个表达式的情况下才行,如果有多于一个表达式,会出现编译错误!

除了以上优化外,Swift还提供了类似shell脚本中的参数速记方法

// $0、$1、$2、$3......,分别指代第一个到第N个参数
reversedNames = names.sorted(by: {$0 > $1 } )

在Swift中String类型也定义了大于号操作符,该操作符的定义正好符合storted方法对闭包的要求,所以还可以简写到令人吃惊的地步

reversedNames = names.sorted(by: > )

7.2 尾闭包优化

  在很多情况下,闭包作为参数传递给方法或函数时,该参数是参数列表的最后一个参数,在此情况下存在优化的空间,以下是具体示例。

// 此函数需要一个闭包参数
func someFunctionThatTakesAClosure(closure: () -> Void) {
    // function body goes here
}

// 以下是不使用尾闭包优化的调用方法
someFunctionThatTakesAClosure(closure: {
    // closure's body goes here
})

// 以下是使用尾闭包优化的调用方法
someFunctionThatTakesAClosure() {
    // trailing closure's body goes here
}

// 如果该闭包参数是唯一的参数,那么可以进一步简化调用方法——省略小括号
someFunctionThatTakesAClosure{
    // trailing closure's body goes here
}

下面是一个更真实有用的例子——把阿拉伯数字转换为英文单词组成的数字

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]

let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

7.3 捕捉值

  与C语言中的函数指针相比,闭包的一大特点是可以捕捉定义其的上下文范围内的值。以下示例展示了捕捉值以及一些细节。

// 定义了一个函数,这个函数有个嵌套函数(实际上就是个闭包),并返回该函数
// 该嵌套函数捕捉了runningTotal、amount两个值
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

// 获得该嵌套函数
let incrementByTen = makeIncrementer(forIncrement: 10)

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

// 获取另外一个嵌套函数
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7

通过以上示例可以看到incrementBySeven、incrementByTen两个常量捕捉的runningTotal、amount值是不同的;
虽然incrementBySeven、incrementByTen是常量,但是其仍然可以修改runningTotal、amount的值,这说明闭包其实是引用类型(后续章节会讲到值类型与引用类型)

7.4 逃逸闭包与非逃逸闭包

  逃逸闭包与非逃逸闭包是仅针对作为函数参数的闭包而言,其他情况下的闭包没有这个属性。

逃逸闭包
在函数执行完毕后,闭包再执行的闭包就是逃逸闭包,其与函数的执行是异步的。

非逃逸闭包
与函数同步执行的闭包是非逃逸闭包。

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

7.5 自动闭包

  顾名思义,自动闭包是由Swift编译器为我们自动创建的闭包,其主要作用是简化代码。

自动闭包示例如下

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"

// customerProvider所代表的闭包没有声明返回值,但是由于remove会返回值,所以该闭包最终被推断为有返回值
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"
上一篇下一篇

猜你喜欢

热点阅读