SwiftNote-6
2016年05月02日 Swift

闭包

Swift支持闭包,相当于其他函数里面的匿名函数的意思。经常是在一些需要传递给其他函数的参数也是一个函数类型的时候,而这个需要被传递的函数又包含的代码是短小的。

创建闭包

  • 闭包语法

    形式就是{(parameter list)-> return type in function implements} in关键字用来分隔函数体与函数类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 用之前函数排序那个例子
    func sortIntArray(inout array:[Int], compare:(Int, Int)->Bool)
    {
    for i in 0 ..< array.count - 1
    {
    for j in 0 ..< array.count - 1 - i
    {
    if compare(array[j], array[j + 1])
    {
    let tmp = array[j]
    array[j] = array[j + 1]
    array[j + 1] = tmp
    }
    }
    }
    }

    var intArray = [1, 3, 10, -1, 100, 99]
    // compare后的参数就是闭包的基本形式,这里不再像之前那样再写一个短小的比较函数
    // 直接使用{}定义一个闭包就可以,效果与之前的lessFunc相同
    sortIntArray(&intArray, compare: {(fir:Int, sec:Int)->Bool in return fir >= sec})

    print(intArray)

    除了infunc关键字,还有函数名基本与定义一个函数类似。

  • 闭包形式简化

    已知的函数类型可以省略函数类型,因为Swift可以自动推断对应的函数类型,只需要知道对应的局部参数名称即可

    1
    2
    3
    4
    5
    // 上例中的compare是明确知道函数类型是(Int, Int)->Bool类型的
    // 上文中的闭可进一步简化成下面的形式
    sortIntArray(&intArray, compare: {(fir, sec) in return fir >= sec})
    // 甚至参数列表的小括号也可以不要
    sortIntArray(&intArray, compare: {fir, sec in return fir >= sec})

    闭包还可以进一步简化,Swift可以隐式的推断单行表达式的返回结果,如上文中的compare参数,需要一个Bool的返回值,而闭包的函数体只有一个单行的表达式,所以这里可以继续把return关键字省略

    1
    2
    //  进一步省略如下
    sortIntArray(&intArray, compare: {(fir, sec) in fir >= sec}) // 去掉return关键字

    还可以再次简化,Swift支持闭包中的参数缩写,即对应使用$ + 对应参数列表中的位置来代表对应的参数列表中的局部参数,比如上文中的fir和sec可以使用 $1和$2来代替,这样就可以把局部参数名称的声明也去掉了,没有函数类型,所以也就不需要in关键字了

    1
    2
    // 参数名称缩写,进一步简化
    sortIntArray(&intArray, compare: ({$1 >= $2})

尾闭包

当闭包表达式是一个参数的最后一个参数时,可以将闭包的{}部分放在函数调用的函数列表之外,以此来增强函数体实现的可读性。

  • 多个参数的尾闭包

    1
    2
    3
    4
    5
    // 上文的例子就是一个尾闭包,闭包表达式是函数参数的最后一个参数
    // 可以这样进行书写
    sortIntArray(&intArray){(fir:Int, sec:Int)->Bool in return fir >= sec}
    // 最后的简化形式,按尾闭包来写
    sortIntArray(&intArray){$1 >= $2}
  • 只有一个参数的尾闭包,甚至还可以去函数调用的小括号去掉,直接接上闭包的{}

    1
    2
    // 如内置的sort函数
    numbers.sort{n2 >= n2} // sort只接受一个函数类型

值捕获

闭包可以捕获其上下文中的变量/常量,即使出了对应的变量/常量已经出了对应的作用域,也能正常保持该值的引用和修改,类似像在一个函数返回另一个嵌套的函数,这个嵌套的函数就可以保持住在原函数中变量/常量的值,并且Swift会全权对闭包内存进行管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func makeIncrementByNum(num:Int)->()->Int
{
var sum = 0
func increment()->Int
{
sum += num
print("increment = \(sum)")
return sum
}
return increment
}

// 值按1递增
let makeInc1 = makeIncrementByNum(1)
makeInc1() // 1
makeInc1() // 2

// 值按10递增,与makeInc1不重合不冲突
let makeInc2 = makeIncrementByNum(10)
makeInc2() // 10
makeInc2() // 20

闭包是引用类型

闭包无论赋值给几个变量或者是常量,闭包内的变量/常量值都是对应函数的引用,不会随着赋值进行拷贝,所以无论你将同一个闭包赋值给几个变量/常量,还是原来的引用

1
2
3
// 如上例中的makeInc1再次赋值给makeInc2
var makeInc3 = makeInc1
makeInc3() // 3 不会产生一个新值的拷贝,因为是原闭包的引用

逃逸/非逃逸闭包

  • 非逃逸闭包

    非逃逸闭包的意思就是闭包传入一个函数中,函数执行完了,这个闭包也就不再起作用了,而逃逸闭包正好相反。Swift中的闭包默认是逃逸的,如果要使一个闭包是非逃逸闭包,在传入函数的参数前使用@noescape标签

    1
    2
    3
    4
    5
    // 上文中sortArray,使compare是非逃逸闭包
    func sortIntArray(inout array:[Int], @noescape compare:(Int, Int)->Bool)
    {
    //...
    }
  • 逃逸闭包

    使闭包逃逸出函数的执行体的办法是将闭包存到一个外部的集合里(如数组,集合),在函数返回的时候再调用对应的集合里的闭包代码,这时如果将对应函数参数标为@noescape的话就会被Swift检测到,会报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 保存要escape的闭包
    var funcDict:[String:()->String] = [:]
    func testEscape(f:()->String)
    {
    funcDict["testEscape"] = f
    print("testEscape function execute completed")
    }

    // 先输出testEscape function execute completed
    // 再输出closure in testEscape
    testEscape{return "closure in testEscape"}
    print(funcDict["testEscape"]!())