闭包基础知识
Swift还有另一个对象,可以用来将代码分解为可重用的块:闭包。
闭包只是一个没有名称的函数;你可以将其分配给一个变量,并像其他任何对象一样传值给它。本章向你展示了闭包是多么的方便和有用。
闭包基础知识
闭包之所以如此命名,是因为它们有能力“关闭”自身范围内的变量和常量。简单的说就是闭包可以访问、存储和操作来自周围上下文的任何变量或常量的值。包括被闭包内的变量和常量。
你可能会问:“如果闭包是没有名称的函数,那么你如何使用它们呢?”要使用闭包,首先必须将其分配给一个变量或常量。
这里是一个变量的声明,它的类型是一个闭包:
var multiplyClosure: (Int, Int) -> Int
multiplyClosure接受两个Int值并返回一个Int,注意这与函数的变量声明完全相同。就像我说的,闭包只是一个没有名字的函数。闭包的类型是函数类型。
你可以将一个闭包分配给这个变量:
multiplyClosure = { (a: Int, b: Int) -> Int in
return a * b
}
这看起来类似于函数声明,但是有一个细微的区别。有相同的参数列表,->符号和返回类型。但是在闭包的情况下,这些元素出现在大括号内,并且在返回类型之后有一个in关键字。
定义了闭包变量之后,就可以使用它,它就像一个函数一样:
let result = multiplyClosure(4, 2)
结果等于8。尽管如此,还是有细微的差别。
注意,闭包没有外部名称的参数。你不能把它们当作函数设置参数名称。
简写语法
与函数相比,闭包被设计为轻量级。有很多方法可以缩短它们的语法。
如果闭包包含一个返回语句,则可以省略return关键字,例如:
multiplyClosure = { (a: Int, b: Int) -> Int in
a*b
}
你可以使用Swift的类型推断,通过删除类型信息来进一步缩短语法:
multiplyClosure = { (a, b) in
a*b
}
请记住,你已经声明了multiplyClosure作为一个闭包,使用两个Int并返回一个Int,因此你可以让Swift为你推断这些类型。
甚至可以省略参数列表。Swift允许你按编号引用每个参数,从0开始,如下所示:
multiplyClosure = {
$0 * $1
}
参数列表、返回类型和关键字都消失了,这样才能使用编号参数。
如果参数列表长得多,那么记住每个编号的参数的意义就会很混乱。在这些情况下,你应该使用命名的语法。
如下面的代码:
func operateOnNumbers(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
let result = operation(a, b)
print(result)
return result
}
声明一个名为operateOnNumbers的函数,它将Int值作为前两个参数。第三个参数命名为operation,它是一个函数类型。operateOnNumbers本身返回一个整数。
然后operateOnNumbers的参数可以使用闭包,比如:
let addClosure = { (a: Int, b: Int) in
a+b
}
operateOnNumbers(4, 2, operation: addClosure)
记住,闭包只是没有名称的函数。所以你不用惊讶他可以像函数一样作为参数传递函数,比如
func addFunction(_ a: Int, _ b: Int) -> Int {
return a + b
}
operateOnNumbers(4, 2, operation: addFunction)
无论operation是一个函数还是一个闭包,operateOnNumbers的调用都是一样的
闭包语法的力量再次派上用场。你可以在operateOnNumbers函数中来定义闭包,如下所示:
operateOnNumbers(4, 2, operation: { (a: Int, b: Int) -> Int in
return a + b
})
没有必要定义闭包并将其分配给局部变量或常量。你可以简单地在函数参数中声明闭包,将其传递到函数中作为参数!
简化闭包语法以删除大量的样板代码。因此,你可以将上述内容减少到以下代码:
operateOnNumbers(4, 2, operation: { $0 + $1 })
甚至可以更进一步。+运算符只是一个包含两个参数并返回一个结果的函数,所以你可以写:
operateOnNumbers(4, 2, operation: +)
还有一种方法可以简化语法,但是只有当最终的参数传递给函数时才能完成。在这种情况下,你可以将闭包移到函数调用之外:
operateOnNumbers(4, 2) {
$0 + $1
}
这可能看起来很奇怪,但它与前面的代码片段相同,只不过你已经删除了operation,并在函数调用参数列表之外拉了括号。这称为尾随闭包语法。
没有返回值的闭包
到目前为止,你所看到的所有闭包都使用了一个或多个参数并返回了值。但是就像函数一样,闭包有时不需要做这些事情。下面是如何声明一个不带参数且不返回任何参数的闭包:
let voidClosure: () -> Void = {
print("Swift Apprentice is awesome!")
}
voidClosure()
闭包的类型是()-> Void。空括号表示没有参数。你必须声明一个返回类型,然后Swift才知道你正在声明一个闭包。这就是Void的用处所在,它的意思正是就是它的名字所表示的:这是一个不返回任何东西的闭包。
注意:Void实际上只是()的一个类型别名。这意味着你可以编写()-> Void as () ->()。然而,函数的参数列表必须始终被括号括起来,所以Void ->()或Void -> Void都是无效的。
闭包范围中的变量或常量
最后,让我们回到闭包的定义特性:它可以从它自己的范围内访问变量和常量。
例如,使用以下闭包:
var counter = 0
let incrementCounter = {
counter += 1
}
incrementCounter相当简单:counter变量增加。counter变量是在闭包之外定义的。闭包可以访问变量,是因为闭包定义在与变量相同的范围内。闭包可以捕获counter变量。它对变量的更改在闭包内并外部都可见。
比方说,你调用闭包5次,就像这样:
incrementCounter()
incrementCounter()
incrementCounter()
incrementCounter()
incrementCounter()
在这五个incrementCounter之后,counter将等于5。
闭包可以用于从封闭范围内捕获变量。例如,你可以编写以下函数:
func countingClosure() -> () -> Int {
var counter = 0
let incrementCounter: () -> Int = {
counter += 1
return counter
}
return incrementCounter
}
该函数不接受参数,但返回一个闭包。它返回的闭包不接受参数但返回Int。
这个函数返回的闭包每次只会增加它的内部counter。每次调用这个函数时,都会得到一个不同的counter。
例如,可以这样使用:
let counter1 = countingClosure()
let counter2 = countingClosure()
counter1() // 1
counter2() // 1
counter1() // 2
counter1() // 3
counter2() // 2
这个函数创建的两个计数器相互独立,独立计数。整洁!