闭包
闭包表达式
一般形式
{ (parameters) -> returnType in
statements
}
Swift 标准库提供了 sort 函数,会根据您提供的排序闭包将已知类型数组中的值进行排序。
下面的闭包表达式示例使用 sort 函数对一个 String 类型的数组进行字母逆序排序,以下是初始数组值:
let names = ["Chris","Alex","Ewa","Barry","Daniella"]
排序函数有两个参数:
①已知类型值的数组。
②一个闭包,采用相同类型的数组的内容的两个参数,并返回一个布尔值来表示是否将第一个值在排序时放到第二个值的前面或是后面。如果第一个值应该出现第二个值之前,闭包需要返回true,否则返回false
该例子对一个 String 类型的数组进行排序,因此排序闭包需为 (String,String) -> Bool 类型的函数
func backwards(s1: String,s2: String) -> Bool {
return s1 > s2 //字母顺序后出现的则较大 如B>A
}
var reversed = sorted(names,backwards)
// reversed is equal to ["Ewa","Daniella","Chris","Alex"]
闭包表达式语法可以使用常量、变量和 inout 类型作为参数,但不提供默认值。 也可以在参数列表的最后使用可变参数。元组也可以作为参数和返回值。
reversed = sorted(names,{ (s1: String,s2: String) -> Bool in
return s1 > s2
})
根据上下文推断类型
因为排序闭包是作为函数的参数进行传入的,Swift可以推断其参数和返回值的类型。 sorted 期望第二个参数是类型为 (String,String) -> Bool 的函数,因此实际上 String,String 和 Bool 类型并不需要作为闭包表达式定义中的一部分。 因为所有的类型都可以被正确推断,返回箭头 (->) 和 围绕在参数周围的括号也可以被省略:
reversed = sorted(names,{ s1,s2 in return s1 > s2 } )
实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。
单行表达式闭包可以通过隐藏 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
reversed = sorted(names,s2 in s1 > s2 } )
在这个例子中,sort 函数的第二个参数函数类型明确了闭包必须返回一个 Bool 类型值。 因为闭包函数体只包含了一个单一表达式 (s1 > s2),该表达式返回 Bool 类型值,因此这里没有歧义,return关键字可以省略。
参数名简写
Swift 自动为内联函数提供了参数名称简写功能,您可以直接通过 $0,$1,$2等名字来引用的闭包的参数的值。
如果您在闭包表达式中使用参数名称简写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称简写的类型会通过函数类型进行推断。 in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
reversed = sorted(names,{ $0 > $1 } )
运算符函数
Swift的 String 类型定义了关于大于号 (>) 的字符串实现,让其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。 而这正好与 sort 函数的第二个参数需要的函数类型相符合。 因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现:
reversed = sorted(names,>)
Trailing闭包(结尾闭包)
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用 trailing 闭包来增强函数的可读性。
Trailing 闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其作为最后一个参数调用。
func someFunctionThatTakesAClosure(closure: () -> ()) { // function body goes here } // here's how you call this function without using a trailing closure: someFunctionThatTakesAClosure({ // closure's body goes here }) // here's how you call this function with a trailing closure instead: someFunctionThatTakesAClosure() { // trailing closure's body goes here }
注意:如果函数只需要闭包表达式一个参数,当您使用 trailing 闭包时,您甚至可以把 () 省略掉。
在上例中作为 sort 函数参数的字符串排序闭包可以改写为
reversed = sorted(names) { $0 > $1 }
下例介绍了如何在 map 方法中使用 trailing 闭包将 Int 类型数组 [16,58,510] 转换为包含对应 String 类型的数组 [“OneSix”,“FiveEight”,“FiveOneZero”]:
let digitNames = [
0: "Zero",1: "One",2: "Two",3: "Three",4: "Four",5: "Five",6: "Six",7: "Seven",8: "Eight",9: "Nine"
]
let numbers = [16,58,510]
let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
//strings 常量被推断为字符串类型数组,即 String[]
//其值为 ["OneSix","FiveEight","FiveOneZero"]
//map 在数组中为每一个元素调用了闭包表达式。 您不需要指定闭包的输入参数 number 的类型,因为可以通过要映射的数组类型进行推断。
捕获(Caputure)
闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数体内的函数。 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
下例为一个叫做 makeIncrementor 的函数,其包含了一个叫做 incrementor 嵌套函数。 嵌套函数 incrementor 从上下文中捕获了两个值,runningTotal 和 amount。 之后 makeIncrementor 将 incrementor 作为闭包返回。 每次调用 incrementor 时,其会以 amount 作为增量增加 runningTotal 的值。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
makeIncrementor 返回类型为 () -> Int。 这意味着其返回的是一个函数,而不是一个简单类型值。 该函数在每次调用时不接受参数只返回一个 Int 类型的值。
makeIncrementor 函数定义了一个整型变量 runningTotal (初始为0) 用来存储当前增加总数。 该值通过 incrementor 返回。
makeIncrementor 有一个 Int 类型的参数,其外部命名为 forIncrement, 内部命名为 amount,表示每次 incrementor 被调用时runningTotal 将要增加的量。
incrementor 函数用来执行实际的增加操作。 该函数简单地使 runningTotal 增加 amount,并将其返回。
在makeIncrementor的嵌套函数incrementor中并没有获取任何参数,却在函数体内访问了runningTotal 和 amount 变量。这是因为其通过捕获在包含它的函数体内已经存在的 runningTotal 和 amount 变量而实现。
由于没有修改 amount 变量,incrementor 实际上捕获并存储了该变量的一个副本,而该副本随着 incrementor 一同被存储。
然而,因为每次调用该函数的时候都会修改 runningTotal 的值,incrementor 捕获了当前 runningTotal 变量的引用,而不是仅仅复制该变量的初始值。捕获一个引用保证了当 makeIncrementor 结束时候并不会消失,也保证了当下一次执行 incrementor 函数时,runningTotal 可以继续增加。
注意:Swift 会决定捕获引用还是拷贝值。 您不需要标注 amount 或者 runningTotal 来声明在嵌入的 incrementor 函数中的使用方式。 Swift 同时也处理 runingTotal 变量的内存管理操作,如果不再被 incrementor 函数使用,则会被清除。
使用makeIncrementor
let incrementByTen = makeIncrementor(forIncrement: 10)
incrementByTen()
// 返回的值为10
incrementByTen()
// 返回的值为20
incrementByTen()
// 返回的值为30
如果您创建了另一个 incrementor,其会有一个属于自己的独立的 runningTotal 变量的引用。 下面的例子中,incrementBySevne 捕获了一个新的 runningTotal 变量,该变量和 incrementByTen 中捕获的变量没有任何联系:
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven() 返回的值为7 incrementByTen() 返回的值为40
注意:如果您闭包分配给一个类实例的属性,并且该闭包通过指向该实例或其成员来捕获了该实例,您将创建一个在闭包和实例间的强引用环。 Swift 使用捕获列表来打破这种强引用环。
闭包是引用类型
上面的例子中,incrementByTen 是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。 这是因为函数和闭包都是引用类型。
无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。 上面的例子中,incrementByTen 指向闭包的引用是一个常量,而并非闭包内容本身。
这也意味着如果您将闭包赋值给了个不同的常量/变量,两个值都会指向同一个闭包:
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回的值为50