Swift:闭包
一、闭包的介绍
- 闭包表达式(Closure Expressions)
- 尾随闭包(Trailing Closures)
- 值捕获(Capturing Values)
- 闭包是引用类型(Closures Are Reference Types)
闭包是自包含的函数代码块,可以在代码中被传递和使用。 Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 lambdas 函数比较相似。
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作。
全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
- 全局函数是一个有名字但不会捕获任何值的闭包
- 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
- 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
- 利用上下文推断参数和返回值类型
- 隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
- 参数名称缩写
- 尾随(Trailing)闭包语法
二、示例
1、sorted 函数,sorted函数需要传入两个参数:
已知类型的数组,闭包函数,该闭包函数需要传入与数组类型相同的两个值,并返回一个布尔类型值来告诉sorted函数当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回true,反之返回false
这是一个相当冗长的方式,本质上只是写了一个单表达式函数 (a > b)
let names = ["Chris","Alex","Ewa","Barry","Daniella"] func backwards(s1:String, s2:String) -> Bool{ return s1 > s2 } var reverse = names.sort(backwards) print("降序:\(reverse)")
2、闭包表达式语法
闭包表达式语法有如下一般形式
{ (parameters) -> returnType in
statements
}
闭包表达式语法可以使用常量、变量和inout类型作为参数,不提供默认值。 也可以在参数列表的最后使用可变参数。 元组也可以作为参数和返回值。
reverse = names.sort({ (s1:String, s2:String) ->Bool in return s1 < s2 }) print("升序:\(reverse)")
3、根据上下文推断类型
因为排序闭包函数是作为sorted函数的参数进行传入的,Swift可以推断其参数和返回值的类型。 sorted期望第二个参数是类型为(String, String) -> Bool的函数,因此实际上String,String和Bool类型并不需要作为闭包表达式定义中的一部分。 因为所有的类型都可以被正确推断,返回箭头 (->) 和围绕在参数周围的括号也可以被省略:
实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。
reverse = names.sort({s1,s2 in return s1 > s2}) print("降序:\(reverse)")
4、单表达式闭包隐式返回
单行表达式闭包可以通过隐藏return关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为
在这个例子中,sorted函数的第二个参数函数类型明确了闭包必须返回一个Bool类型值。 因为闭包函数体只包含了一个单一表达式 (s1 > s2),该表达式返回Bool类型值,因此这里没有歧义,return关键字可以省略。
reverse = names.sort({s1,s2 in s1 < s2}) print("升序:\(reverse)")
5、参数名称缩写
Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数。
如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。 in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
reverse = names.sort({ $0 > $1 })//在这个例子中,$0和$1表示闭包中第一个和第二个String类型的参数。 print("降序:\(reverse)")
6、运算符函数
实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。 Swift 的String类型定义了关于大于号 (>) 的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。 而这正好与sorted函数的第二个参数需要的函数类型相符合。 因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现:
reverse = names.sort( < ) print("升序:\(reverse)")
7、尾随闭包
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。 尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
func someFunctionThatTakesAClosure(closure: () -> ()) { // 函数体部分 closure() }
//以下是不使用尾随闭包进行函数调用 someFunctionThatTakesAClosure({ //闭包主体部分 print("first:closure闭包函数被调用了") }) //以下是使用尾随闭包进行函数调用 someFunctionThatTakesAClosure { //闭包主体部分 print("second:closure闭包函数被调用了") }
注意: 如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉。
例如:reverse = names.sort({ $0 > $1 })
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。 举例来说,Swift 的Array类型有一个map方法,其获取一个闭包表达式作为其唯一参数。 数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。 具体的映射方式和返回值类型由闭包来指定。
当提供给数组闭包函数后,map方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。
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] func printOutput() -> Void { let strings = numbers.map { (var number) -> String in var output = "" while number > 0 { output = digitNames[number % 10]! + output number /= 10 } return output } print(strings) } printOutput()
8、捕获值
闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
func makeIncrementor(forIncrement amount: Int) -> () -> Int{ var runningTotal = 0 func incrementor() -> Int{ runningTotal += amount return runningTotal } return incrementor }
注意: Swift 会决定捕获引用还是拷贝值。 您不需要标注amount或者runningTotal来声明在嵌入的incrementor函数中的使用方式。 Swift 同时也处理runingTotal变量的内存管理操作,如果不再被incrementor函数使用,则会被清除。
let incrementByTen = makeIncrementor(forIncrement: 10) print(incrementByTen()) print(incrementByTen()) print(incrementByTen())
三、打印结果:
降序:["Ewa", "Daniella", "Chris", "Barry", "Alex"] 升序:["Alex", "Barry", "Chris", "Daniella", "Ewa"] 降序:["Ewa", "Daniella", "Chris", "Barry", "Alex"] 升序:["Alex", "Barry", "Chris", "Daniella", "Ewa"] 降序:["Ewa", "Daniella", "Chris", "Barry", "Alex"] 升序:["Alex", "Barry", "Chris", "Daniella", "Ewa"] first:closure闭包函数被调用了 second:closure闭包函数被调用了 ["OneSix", "FiveEight", "FiveOneZero"] 10 20 30