在Swift中,闭包是一个非常重要的概念,它跟java的lambda表达式类似,也跟其他语言中的闭包相差无几。

在介绍闭包前要先介绍一下Swift中的函数类型。在Swift中,函数跟普通的变量一样也是有类型这个概念的,函数的类型就是它的参数和返回值,比如下面两个函数:

func add(a: Int, b: Int) -> Int {
    return a + b
}
func sub(a: Int, b: Int) -> Int {
    return a - b
}

它们两个的类型都是(Int, Int) -> Int。所以函数是可以赋值给一个变量的,那这个变量又有什么用呢?那再看看下面这个代码:

var calFun: (Int, Int) -> Int = add
print(calFun(3,5))  //输出8
calFun = sub
print(calFun(3,5))  //输出-2

由以上示例可知,当把函数赋值给一个变量或常量时,这个变量或常量就可以当作一个函数使用,其功能与该函数相同。

那接下来介绍一下闭包,闭包又是什么呢?简单来说,闭包就是一个自包含的函数代码块,它可以作为函数的参数和返回值来使用,其格式如下:

{ (参数) -> 返回值类型 in 
代码块
 }  //当闭包没有参数和返回值时,需要省略in;否则必须加上in

下面通过一个作业来体验一下闭包从复杂到简单的过程:

要求:找出1到10000所有的质数,将找出的质数存入可辨数组,对数组进行正反排序并输出结果

func findPrimNumber() -> [Int] {
    var num = [2, 3]
    var flag = true
    var sqr = 0
    for x in 4...10000 {
        sqr = Int(sqrt(Double(x)))
        for y in 2...sqr {
            if x % y == 0 {
                flag = false
                break
            }
        }
        if flag {
            num.append(x)
        }
        flag = true
    }
    return num
} //返回1到10000中所有的质数

var numbers = findPrimNumber()
numbers.sort() //升序排序

//降序排序
/// mark: method 1: 自定义函数的闭包,将函数作为参数传递进排序函数中
func descending(x: Int, y: Int) -> Bool {
    return x > y
}
numbers.sort(by: descending)

/// mark: method 2: 指定参数名和类型的闭包,这个是最标准的闭包的语法
numbers.sort { (x: Int, y: Int) -> Bool in
    return x > y
}

/// mark: method 3: 利用推断只指定参数名的闭包,当定义函数参数时,肯定会定义传入的闭包的类型,所以可以根据上下文推断闭包中参数的类型
numbers.sort { (x, y) -> Bool in
    return x > y
}

/// mark: method 4: 利用推断省略返回值类型的闭包,同第三种方法,返回值类型也可以通过上下文推断
numbers.sort { (x, y) in
    return x > y
}

/// mark: method 5: 利用推断省略参数和返回值的闭包,同第三种方法,参数类型和返回值类型都可以通过上下文推断,所以可以使用一个语法糖来表示参数,其中$0表示第一个参数,$1表示第二个参数,如果还有更多的参数,依次类推
numbers.sort(by: { return $0 > $1 })

/// mark: method 6: 省略return的闭包,当闭包中只有一句return语句时,可以省略return
numbers.sort { $0 > $1 }

/// mark: method 7: 传入操作符函数的闭包,在Swift中,操作符也定义为函数,所以操作符也可以作闭包使用
numbers.sort(by: >)

接下来再介绍一个概念:尾随闭包。先上代码:

func add(a: Int, b: Int, op: (Int, Int) -> Int) {
    print(op(a,b))
}
add(a: 1, b: 2){ $0 + $1 }  //输出3
//尾随闭包的好处是当闭包内的代码过多时,看起来更简洁、美观。

尾随闭包就是当闭包作为函数的最后一个参数,在调用该时可以将闭包写在函数的最后面(参数之后)。这样的好处就是当函数参数过多或者闭包中代码过多时,让代码看起来更简洁、美观。

最后再介绍一下闭包的值捕获:

先来看一段代码:

var a = 10
func add() {
    a += 1
}
print(a)  //输出结果为10
print(a)  //输出结果仍为10

这个代码很容易理解,在函数外定义了一个全局的变量a,在函数中更改它的值,最后再输出。可以看到在函数外面两次输出时该变量都没有变化,也就是说。是因为该变量在函数中被改变时,改变的只是这个变量的副本,而对其本身并没有影响。

那再看一段代码:

func getIncFunc(inc: Int) -> (Int) -> Int {
    var mt = 10
    func incFunc(v: Int) -> Int {
        mt += 1
        return inc + v + mt
    }
    return incFunc
}
var incFunc1 = getIncFunc(inc: 3)

print(incFunc1(10))  //输出结果为24
print(incFunc1(10))  //输出结果为25

incFunc1 = getIncFunc(inc: 3)
print(incFunc1(10))  //输出结果为24

很多人就觉得很奇怪了,为什么三次输出,有两次为24,但怎么会有一次是25呢?原因如下:

闭包中(嵌套函数一样)是引用类型,当包含该闭包的函数在被销毁之前(引用计数大于0),该函数中被闭包使用的局部变量在调用之后会留下一个副本,该闭包会一直捕获到这个值,并在以后的调用中使用。

而在最后一个输出时结果为24,这是因为在这之前,incFunc1被重新赋值,在赋值的过程中,会先将该变量之前的值销毁,并重新开辟空间给该变量,所以之前闭包捕获的值被释放,最后的结果才是24.