Swift - 闭包
前言
1 - 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块 blocks 以及其他一些编程语言中的 lambdas 函数比较相似
2 - 闭包可以捕获和存储其所在上下文中任意常量和变量的引用, 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作
3 - 全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一
① 全局函数是一个有名字但不会捕获任何值的闭包
② 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
③ 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
4 - Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下
① 利用上下文推断参数和返回值类型
② 隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字
③ 参数名称缩写
④ 尾随闭包语法
闭包
1 - 闭包表达式语法有如下一般形式:闭包表达式语法可以使用常量、变量和 inout 类型作为参数,不提供默认值。元组也可以作为参数和返回值
{ (parameters) -> returnType in statements }
2 - 如何使用闭包
① 数组排序:分别以函数、闭包两种方式实现
1 // 搞一个数组完成排序 2 let names = ["AT", "AE", "D", "S", "BE"] 3 4 // 方式一:使用普通函数完成排序 5 // 其实这是一个相当冗长的方式:本质上只是写了一个单表达式函数 x > y 6 func backwards(s1: String, s2: String) -> Bool { 7 return s1 > s2 8 } 9 // sorted 函数需要传入两个参数 10 // 1 已知类型的数组 11 // 2 闭包函数:该闭包函数需要传入与数组类型相同的两个值 12 var reversed = names.sorted(by: backwards) 13 print(reversed) // ["S", "D", "BE", "AT", "AE"] 14 15 16 // 方式二:使用闭包 17 reversed = names.sorted(by: { 18 19 // 闭包的函数体部分由关键字 in 引入 20 // 该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始 21 (s1: String, s2: String) -> Bool in 22 return s1 > s2 23 }) 24 25 // 这里的闭包的函数体部很短,可以将其改写成一行代码 26 reversed = names.sorted(by: {(s1: String, s2: String) -> Bool in return s1 > s2}) 27 print(reversed) // ["S", "D", "BE", "AT", "AE"]
② 闭包可以根据上下文推断类型
// Swift 可以推断其参数和返回值的类型,因为排序闭包函数是作为 sorted 函数的参数进行传入的 // 正是因为所有的类型都可以被正确推断,返回箭头 -> 和围绕在参数周围的括号也可以被省略 reversed = names.sorted(by:{ s1, s2 in return s1 > s2 })
注:通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型
③ 单行表达式闭包隐式返回:可以通过隐藏 return 关键字来隐式返回单行表达式的结果
reversed = names.sorted(by:{ s1, s2 in s1 > s2 }) // 因为 sorted 函数的第二个参数函数类型明确了闭包必须返回一个 Bool 类型值 // 且闭包函数体只包含了一个单一表达式 s1 > s2,该表达式返回 Bool 类型值 // 这里省略 return 关键字完全没有任何歧义
④ 参数名称缩写:Swift 自动为内联函数提供了参数名称缩写功能,可以直接通过 $0、$1、$2 来顺序调用闭包的参数
// 如果在闭包表达式中使用参数名称缩写,可以在闭包参数列表中省略对其的定义 // 应参数名称缩写的类型会通过函数类型进行推断 // in 关键字也同样可以被省略 reversed = names.sorted(by:{ $0 > $1 }) // $0 和 $1 分别表示闭包中第一个、第二个参数
⑤ 运算符函数:是一种更简短闭包表达式
// String 类型定义了关于大于号 > 的字符串实现 // 其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值 // 而这正好与 sorted 函数的第二个参数需要的函数类型相符合 // 因此只需简单地传递一个大于号,Swift 就可以自动推断出您想使用大于号的字符串函数实现 reversed = names.sorted(by:<) // 为作区分,这里使用小于号 < print(reversed) // ["AE", "AT", "BE", "D", "S"]
⑥ 尾随闭包:如果将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用
// 字符串排序闭包可以改写成 reversed = names.sorted(by:){ $0 > $1 } print(reversed)
注: 为什么使用看似鸡肋的尾随闭包 ?当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用
⑦ 捕获值:闭包可以在其定义的上下文中捕获常量或变量
1 // 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量 2 func makeIncrementor(forIncrement amount: Int) -> () -> Int { 3 4 var runningTotal = 3 5 func incrementor() -> Int { 6 runningTotal += amount 7 return runningTotal 8 } 9 return incrementor 10 } 11 12 // 嵌套函数 incrementor 从上下文中捕获了两个值:runningTotal 和 amount 13 print(makeIncrementor(forIncrement: 12)()) // 15 14 15 let incrementByTen = makeIncrementor(forIncrement: 10) 16 print(incrementByTen()) // 13 17 print(incrementByTen()) // 23 18 print(incrementByTen()) // 33 19 20 21 // 如果创建了另一个 incrementor,其会有一个属于自己的独立的 runningTotal 变量的引用 22 // incrementBySevne 捕获了一个新的 runningTotal 变量 23 // 该变量和 incrementByTen 中捕获的变量没有任何联系 24 let incrementBySeven = makeIncrementor(forIncrement: 7) 25 print(incrementBySeven()) // 10 26 print(incrementByTen()) // 43 27 28 // 闭包是引用类型 29 // incrementBySeven 和 incrementByTen 是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。这是因为函数和闭包都是引用类型 30 // 这意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包 31 let alsoIncrementByTen = incrementByTen 32 print(alsoIncrementByTen()) // 53