闭包是自包含的功能块,可以在代码中传递和使用。Swift中的闭包类似于Objective-C中的块以及其他编程语言中的lambdas。闭包也类似于C语言中的函数指针,但是其比函数指针强大。闭包定义它们的上下文中捕获和存储对任何常量和变量的引用,Swift为您处理捕获的所有内存管理。
闭包有三种形式:
- 全局函数是具有名称但不捕获任何值的闭包。
- 嵌套函数是具有名称的闭包,可以从其封闭函数中捕获值。
- Closure表达式是一种未命名的闭包,用轻量级语法编写,可以从周围的上下文中捕获值。
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 逃逸闭包与非逃逸闭包
逃逸闭包与非逃逸闭包是仅针对作为函数参数的闭包而言
,其他情况下的闭包没有这个属性。
- @escaping 逃逸闭包
- @noescaping 非逃逸闭包——默认值
逃逸闭包
在函数执行完毕后,闭包再执行的闭包就是逃逸闭包,其与函数的执行是异步的。
非逃逸闭包
与函数同步执行的闭包是非逃逸闭包。
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"
网友评论