Swift编程入门(12)闭包

2019/01/09 21:00 下午 posted in  Swift入门

闭包(closure)是在应用中完成特定任务的互相分离的功能组。上一章学习的函数是闭包的
特殊情况,可以把函数理解为有名字的闭包。

闭包的语法

想象你是一个社区组织者,负责管理若干组织。你想记录每个组织有多少名志愿者,因此创建了一个数组来完成这个任务.

import Cocoa
let volunteerCounts = [1,3,40,32,2,53,77,13]

你输入了每个组织提供的志愿者人数,这意味着这个数组是完全无序的。如果志愿者数组能按人数从小到大排序就好了。好消息是,Swift提供了sorted(by:)方法来指定如何排列数组。
sorted(by:)接受一个参数:一个描述如何排序的闭包。这个闭包接受两个参数,其类型必须和数组元素的类型匹配,并返回布尔值。通过比较两个参数会生成返回值,表示第一个参数是否排在第二个参数前面。如果想让参数一排在参数二前面,可以在返回的时候使用<;这样会把数组按升序(ascending)排列,也就是从小到大排序。如果想让参数二排在参数一前面,可以在返回的时候使用>;这样会把数组按降序(desending)排列,也就是从大到小排序
因为志愿者人数的数组中都是整数,所以sorted(by:)的函数类型应该类似于((Int, Int) -> Bool) -> [Int]。读出来就是“sorted(by:)是一个接受两个整数进行比较并返回布尔值表示哪个整数在前的闭包”。sorted(by:)返回一个新的整数数组,这些整数已经根据闭包定义的规则排好序了。

import Cocoa 
let volunteerCounts = [1,3,40,32,2,53,77,13] 
func sortAscending(_ i: Int, _ j: Int) -> Bool { 
    return i < j 
} 
let volunteersSorted = volunteerCounts.sorted(by: sortAscending) 

首先,我们创建了函数sortAscending(_:_:)。这个函数比较两个整数并返回一个布尔值表示整数i是否应该在整数j前面。因为sortAscending这个名字已经意味着我们是在对两个实例排序,所以可以用_省去在调用时的参数名。如果i小于j从而应该排在j前面的话,这个函数就会返回true。这个全局函数是一个命名闭包(回忆一下,所有的函数都是闭包),因此可以将其作为sorted(by:)的参数。
接着,调用sorted(by:),把sortAscending(::)作为第二个参数传递。因为sorted(by:)返回一个新数组,所以需要把结果赋给新的常量数组volunteersSorted。这个实例保存组织内排过序的志愿者人数。

闭包表达式语法

{(parameters) -> return type in 
    // 代码
} 

闭包表达式写在花括号({})里。紧跟着左花括号的圆括号里是闭包的参数。闭包的返回值类型在参数后面,和常规语法一样。关键字in用来分隔闭包的参数、返回值与闭包体内的语句。
重构上面排序的代码

import Cocoa 
let volunteerCounts = [1,3,40,32,2,53,77,13] 

let volunteersSorted = volunteerCounts.sorted(by: { 
(i: Int, j: Int) -> Bool in 
return i < j 
}) 

这段代码比第一个版本稍微清晰和优雅一些。我们没有提供在playground中其他地方定义的函数,而是在sorted(by:)方法的第二个参数中实现了一个内联闭包。我们在闭包的圆括号中定义了其参数及类型(Int),也指定了返回值类型。接着,用逻辑测试(i是否比j小?)来确定闭包的返回值,从而实现闭包体。
结果跟之前一样:把排好序的数组赋给了volunteersSorted。
这次重构在正确的方向上迈进了一步,但还是有点冗长。闭包可以利用Swift的类型推断系统,因此我们可以通过去除类型信息来进一步简化闭包。

import Cocoa 
let volunteerCounts = [1,3,40,32,2,53,77,13] 
let volunteersSorted = volunteerCounts.sorted(by: { i, j in i < j })

这段代码改动了三处。首先,移除了两个参数和返回值的类型信息。返回值类型可以移除是因为编译器知道检查i < j是否成立会返回布尔值true或false。其次,把整个闭包表达式放到了一行。
最后,移除了关键字return。不是所有的闭包语句都可以省略return关键字,这里可以是因为只有一个表达式(i < j)。如果存在更多表达式,那么显式的return就是必需的。
这个闭包现在变得很紧凑,但是还可以继续优化。Swift提供了快捷参数名,可以在内联闭包表达式中引用。这些快捷参数名和显式声明的参数类似:类型和值都一样。编译器的类型推断能力让它知道闭包接受的参数个数和类型,这意味着不需要给参数命名。
举个例子,编译器知道sorted(by:)接受一个闭包。这个闭包本身又接受两个参数,它们的类型和sorted(by:)方法的数组参数中的元素类型一样。因为闭包有两个参数,我们能比较其值来判断顺序,所以可以用$0引用第一个参数的值,用$1引用第二个参数的值。

import Cocoa 
let volunteerCounts = [1,3,40,32,2,53,77,13] 
let volunteersSorted = volunteerCounts.sorted(by: { $0 < $1 })

这种尾部闭包语法(trailing closure syntax)对于闭包体很长的情况特别有用。在这里,尾部闭包只是让我们少输入了两个圆括号。

函数作为返回值

函数作为参数

闭包能捕获变量

闭包是引用类型

函数式编程