一、闭包的概念和分类
闭包:自包含的函数代码块,可以在代码中被传递和使用。
注意:在函数那篇文章中介绍的全局函数,实际上也是特殊的闭包
1.全局函数是一个有名字但不会捕获任何值的闭包
2.嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
3.闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
我们这里说的闭包一般更多指的是闭包表达式(匿名)
二、闭包表达式的语法
在上篇文章中,我们知道对于函数是这样定义的:
func funcName(参数) ->返回值类型{
执行语句
}
闭包表达式的语法:
{ (参数)->返回值 in
执行语句
}
举例子1:
这是一个无参数无返回值的闭包表达式
let sayhello = {
print("hello world")
}
//调用
sayhello()
输出:
hello world
注意:我们发现无参数无返回值的闭包表达式可以省略掉**in**
举例子2:
这是一个有参数有返回值的闭包表达式
let add = {(a: Int, b: Int) -> Int in
return a + b
}
//调用
print(add(2,1))
//输出:
3
上面代码可以添加闭包表达式的类型,提高可读性,如下:
let add1:(Int, Int) -> Int = {(a: Int, b: Int) -> Int in
return a + b
}
注意:闭包表达式的类型就是参数和返回值的类型(类似函数)
三、闭包表达式作为回调函数
闭包表达式赋值给一个常量或者变量这种情况很少见(本文第二点中介绍的),更多用作回调函数
如下,我们先用代码来实现一个冒泡排序:
/* 打印数组函数*/
func showArray(array:[Int]){
for i in array {
print("\(i)")
}
print("\n")
}
/* 冒泡排序函数*/
func bubbleSort( array: inout [Int]){
let cnt = array.count
for i in 1 ..< cnt {
for j in 0 ..< cnt-i {
if (array[j] > array[j + 1]) {//假如我们的需求变为比较数组个位数的大小时,就需要修改这个函数本身的结构。为了避免这种情况我们可以改写这个函数,传入一个闭包表达式(比较策略),来使得这个函数更通用,在具体情况下只需修改闭包表达式(比较策略)就可以
let t = array[j]
array[j] = array[j + 1]
array[j + 1] = t
}
}
}
}
*******调用*******
var array = [20,2,3,7,70,8]
showArray(array: array)//排序前输出
排序前输出:
3
20
2
3
7
70
8
bubbleSort(array: &array)//排序
showArray(array: array)//排序后输出
排序后输出:
2
3
7
8
20
70
从上面的代码我们可以看出,这是基本的冒泡排序。这种情况下,当我们需要修改排序的策略时,就得回到bubbleSort这个主函数中去修改排序策略。为了避免这种情况,这时我们可以利用Swift中的闭包解决。把上面代码修改如下:
/* 携带闭包参数的 冒泡排序函数*/
func bubbleSort1( array: inout [Int],cmp:(Int, Int) -> Int){//修改排序函数,多加一个闭包作为函数的第二个参数
let cnt = array.count
for i in 1 ..< cnt {
for j in 0 ..< cnt-i {
if (cmp(array[j], array[j + 1]) == -1) {//把数组做比较的两个元素传给cmp闭包,让闭包去处理排序(这时闭包影响排序的策略),当我们修改排序策略时,不需要修改这个主函数的结构,只需修改cmp闭包即可
let t = array[j]
array[j] = array[j + 1]
array[j + 1] = t
}
}
}
}
/* 定义一个闭包,这时该闭包影响函数的排序策略,作为bubbleSort1的参数*/
let intCmp = {
(a: Int, b: Int) -> Int in
if a > b {//升序
return -1
}else if a < b {//降序
return 1
}else{
return 0
}
}
*******调用*******
var array1 = [2,4,1,7,123,5,88];
showArray(array: array1)
排序前输出:
**2**
**4**
**1**
**7**
**123**
**5**
**88**
bubbleSort1(array: &array1 , cmp: intCmp)//排序
showArray(array: array1)
排序后输出:
**1**
**2**
**4**
**5**
**7**
**88**
**123**
这时我们可以看到,经过修改后的代码最终得到的效果时一样的,并且我们把排序的策略通过闭包分离出来了。
这时,当我们的排序策略改为了按数组元素的个位数数值排序,则我们只需修改闭包代码即可,修改后的代码如下:
//主函数不需修改,跟上面的一样
func bubbleSort1( array: inout [Int],cmp:(Int, Int) -> Int){//修改排序函数,多加一个闭包作为函数的第二个参数
let cnt = array.count
for i in 1 ..< cnt {
for j in 0 ..< cnt-i {
if (cmp(array[j], array[j + 1]) == -1) {//把数组做比较的两个元素传给cmp闭包,让闭包去处理排序(这时闭包影响排序的策略),当我们修改排序策略时,不需要修改这个主函数的结构,只需修改cmp闭包即可
let t = array[j]
array[j] = array[j + 1]
array[j + 1] = t
}
}
}
}
/* 调整闭包的策略即可*/
let intCmp = {
(a: Int, b: Int) -> Int in
let x = a % 10, y = b % 10
if x > y {
return -1
}else if x < y {
return 1
}else{
return 0
}
}}
*******调用*******
var array1 = [2,4,1,7,123,5,88];
showArray(array: array1)
排序前输出:
**2**
**4**
**1**
**7**
**123**
**5**
**88**
bubbleSort1(array: &array1 , cmp: intCmp)
showArray(array: array1)
排序后输出:
**1**
**2**
**123**
**4**
**5**
**7**
**88**
这时我们可以看到,经过修改闭包的策略,函数能按我们的需求进行排序按个位数升序
一般我们在Swift中使用闭包时,不需要像上面一样,给闭包表达式赋值给一个变量或者常量,然后在传给主函数。而是在函数调用时,直接把闭包作为实参传入主函数中。例如下面这种写法,在Swift中非常常见,也就是我们所说的轻量级函数:
//在调用bubbleSort1函数时,直接把闭包cmp作为函数的实参。
//对与bubbleSort1而言,cmp这个函数怎么实现的它并不关心,它只知道这个函数是由别人实现的,我可以调用,我们称之为回调,所以cmp就相当于bubbleSort1的一个回调函数。
//对于bubbleSort1的使用者来讲,尽管bubbleSort1的实现原理他不知道,他只知道这是一个排序(不需要知道排序的原理),这时需要bubbleSort1的使用者传递一个策略来告诉bubbleSort1怎么排。
bubbleSort1(array: &array1 , cmp: {
(a: Int, b: Int) -> Int in
let x = a % 10, y = b % 10
if x > y {
return -1
}else if x < y {
return 1
}else{
return 0
}}
)
四、闭包表达式语法优化
如本文中的第三节中,** bubbleSort1函数在定义的时候就已经给出闭包表达式的类型了,这时根据Swift强大的类型推断,我们在调用 bubbleSort1函数传入闭包表达式时可以优化其类型和返回值**。
1.省略掉闭包表达式的参数类型
bubbleSort1(array: &array1 , cmp: {
(a, b) -> Int in
let x = a % 10, y = b % 10
if x > y {
return -1
}else if x < y {
return 1
}else{
return 0
}}
)
2.省略掉闭包表达式的返回值
bubbleSort1(array: &array1 , cmp: {
(a, b) in
let x = a % 10, y = b % 10
if x > y {
return -1
}else if x < y {
return 1
}else{
return 0
}}
)
3.省略掉闭包表达式参数名称,使用$0、$1、$2...来表示
bubbleSort1(array: &array1 , cmp: {
let x = $0 % 10, y = $1 % 10
if x > y {
return -1
}else if x < y {
return 1
}else{
return 0
}}
)
五、尾随闭包
Swift有一个不成文的规定,我们在声明一个函数的时候,我们应该把闭包参数作为整个函数形参的最后一个参数。当闭包表达式作为函数的最后一个参数时,我们可以优化写法为(就是所谓的尾随闭包):
bubbleSort1(array: &array1 ){
let x = $0 % 10, y = $1 % 10
if x > y {
return -1
}else if x < y {
return 1
}else{
return 0
}}
六、嵌套函数
在函数内部定义和实现的函数。如下:
func bubbleSortFunc(array: inout [Int]){
let cnt = array.count
//在bubbleSortFunc内部声明一个交换两个变量的函数swapValue,swapValue只作用于bubbleSortFunc内部,这就是Swift的嵌套函数
func swapValue(a: inout Int, b: inout Int){
let t = a
a = b
b = t
}
for i in 1 ..< cnt {
for j in 0 ..< cnt - i {
if (array[j] > array[j + 1]) {
swapValue(a: &array[j], b: &array[j + 1])
}
}
}
}
*******调用*******
var arrayTest = [42,13,435,2,67,345]
showArray(array: arrayTest)
排序前输出:
42
13
435
2
67
345
bubbleSortFunc(array: &arrayTest)
showArray(array: arrayTest)
排序后输出:
2
13
42
67
345
435
说明:swapValue函数在函数** bubbleSortFunc内部声明和实现,只作用于 bubbleSortFunc**内部
7、闭包捕获值
一个闭包能够从上下文捕获已被定义的常量和变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。
//定义一个函数,这个函数有一个Int参数,并且返回一个(Int) -> Int类型的闭包
func getIncFunc(inc: Int) -> (Int) -> Int{
func incFunc(v: Int) -> Int{//该函数返回一个Int类型
return inc + v
}
return incFunc//返回incFunc函数
}
*****调用*****
let incFunc1 = getIncFunc(inc: 3)
print(incFunc1(10))
输出:
13
说明:首我们调用了getIncFunc函数,传入3,这时返回一个 (Int) -> Int的函数,该函数内部的inc被赋值3,这时我们再调用incFunc1函数,传入参数10,这时该函数内部的v被赋值为10,最终inc+v = 3 + 10 = 13。这种情况下,我们发现函数内部的inc不是incFunc1定义的但在incFunc1被调用时inc值被捕获到了。