【Swift学习】Swift编程之旅---闭包(十一)

  闭包是能够在代码中被传递和使用的自包含功能模块,它很像c和oc中的block,和.net中的lambdasbas表达式。html

  闭包能够捕获和存储其所在上下文中任意常量和变量的引用。 包裹着这些常量和变量的包俗称闭包。Swift会为您管理在捕获过程当中涉及到的内存操做。下面是闭包的3中表现形式ios

  • 全局函数是一个有名字但不会捕获任何值的闭包
  • 嵌套函数是一个有名字并能够捕获其封闭函数域内值的闭包
  • 闭包表达式是一个利用轻量级语法所写的能够捕获其上下文中变量或常量值的没有名字的闭包

  Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中以实现语法优化,主要优化以下: git

  •  利用上下文推断参数和返回值类型
  •  单表达式(single-expression)闭包能够省略 return 关键字
  •  参数名称简写
  •  Trailing 闭包语法
 
   闭包表达式
   嵌套函数是一种在较复杂函数中方便进行命名和定义自包含代码模块的方式。 固然,有时候撰写小巧的没有完整定义和命名的类函数结构也是颇有用处的,尤为是在处理一些函数并须要将另一些函数做为该函数的参数时。下面闭包表达式的例子经过使用几回迭代展现了 sort 函数定义和语法优化的方式
 
   sort方法
  
Swift 标准库提供了 sort 函数,会根据您提供的排序闭包将已知类型数组中的值进行排序。 一旦排序完成,函数会返回一个与原数组大小相同的新数组,该数组中包含已经正确排序的同类型元素。
下面的闭包表达式示例使用 sort 函数对一个 String类型的数组进行字母逆序排序,如下是初始数组值:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

 

排序函数有两个参数:
1. 已知类型值的数组。
2. 一个闭包,采用相同类型的数组的内容的两个参数,并返回一个布尔值来表示是否将第一个值在排序时放到第二个值的前面或是后面。若是第一个值应该出现第二个值以前,闭包须要返回true,不然返回false。
 
该例子对一个  String 类型的数组进行降序排序,所以排序闭包需为 (String, String) -> Bool 类型的函数。
func backwards(s1: String, _ s2: String) -> Bool { return s1 > s2 } var reversed = names.sort(backwards) // reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

 

若是第一个字符串 (s1) 大于第二个字符串 (s2),backwards 函数则返回 true,表示在新的数组中 s1 应该出如今 s2 前。 字符中的 "大于" 表示 "按照字母顺序后出现"。 这意味着字母 "B" 大于字母 "A", 字符串 "Tom" 大于字符串 "Tim"。 其将进行字母逆序排序,"Barry" 将会排在 "Alex" 以前,依次类推。
 
然而,这是一个至关冗长的方式,本质上只是写了一个单表达式函数 (a > b)。 在下面的例子中,利用闭合表达式语法能够更好的构造一个内联排序闭包。
 
 
  闭包表达式语法
闭包表达式语法可使用常量、变量和 inout 类型做为参数,但不提供默认值。 也能够在参数列表的最后使用可变参数。元组也能够做为参数和返回值。
reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 }) 

 

须要注意的是内联闭包参数和返回值类型声明与 backwards 函数类型声明相同。 在这两种方式中,都写成了 (s1: String, s2: String) -> Bool类型。 然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
 
闭包的函数体部分由关键字 in 引入。 该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
 
由于这个闭包的函数体部分如此短以致于能够将其改写成一行代码
reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } )

 这说明 sort 函数的总体调用保持不变,一对圆括号仍然包裹住了函数中整个参数集合。而其中一个参数如今变成了内联闭包 。express

 
   根据上下文推断类型
  由于排序闭包是做为函数的参数进行传入的,Swift能够推断其参数和返回值的类型。 sort 指望第二个参数是类型为 (String, String) -> Bool 的函数,所以实际上 String, String 和 Bool 类型并不须要做为闭包表达式定义中的一部分。 由于全部的类型均可以被正确推断,返回箭头 (->) 和 围绕在参数周围的括号也能够被省略:
reversed = sort(names, { s1, s2 in return s1 > s2 } ) 

 

际上任何状况下,经过内联闭包表达式构造的闭包做为参数传递给函数时,均可以推断出闭包的参数和返回值类型,这意味着您几乎不须要利用完整格式构造任何内联闭包。
 
然而,你也可使用明确的类型,若是你想它避免读者阅读可能存在的歧义,这样仍是值得鼓励的。这个排序函数例子,闭包的目的是很明确的,即排序被替换,并且对读者来讲能够安全的假设闭包可能会使用字符串值,由于它正协助一个字符串数组进行排序。
 
   单行表达式闭包能够省略 return
单行表达式闭包能够经过隐藏 return 关键字来隐式返回单行表达式的结果,如上版本的例子能够改写为:
reversed = sort(names, { s1, s2 in s1 > s2 } ) 

 

在这个例子中,sort 函数的第二个参数函数类型明确了闭包必须返回一个 Bool 类型值。 由于闭包函数体只包含了一个单一表达式 (s1 > s2),该表达式返回 Bool 类型值,所以这里没有歧义,return关键字能够省略。
 
  参数名简写
Swift 自动为内联函数提供了参数名称简写功能,您能够直接经过 $0,$1,$2等名字来引用的闭包的参数的值。
 
若是您在闭包表达式中使用参数名称简写,您能够在闭包参数列表中省略对其的定义,而且对应参数名称简写的类型会经过函数类型进行推断。 in 关键字也一样能够被省略,由于此时闭包表达式彻底由闭包函数体构成:
 
 
reversed = sort(names, { $0 > $1 } ) 

 

  运算符函数数组

实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。 Swift的 String 类型定义了关于大于号 (>) 的字符串实现,让其做为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。 而这正好与 sort 函数的第二个参数须要的函数类型相符合。 所以,您能够简单地传递一个大于号,Swift能够自动推断出您想使用大于号的字符串函数实现:
reversed = sort(names, >) 

 

    Trailing 闭包
若是您须要将一个很长的闭包表达式做为最后一个参数传递给函数,可使用 trailing 闭包来加强函数的可读性。
 
Trailing 闭包是一个书写在函数括号以外(以后)的闭包表达式,函数支持将其做为最后一个参数调用。
 
func someFunctionThatTakesAClosure(closure: () -> ()) { // 函数体部分  } // 如下是不使用 trailing 闭包进行函数调用   someFunctionThatTakesAClosure({ // 闭包主体部分  }) // 如下是使用 trailing 闭包进行函数调用   someFunctionThatTakesAClosure() { // 闭包主体部分  } 

 注意:若是函数只须要闭包表达式一个参数,当您使用 trailing 闭包时,您甚至能够把 () 省略掉。安全

在上例中做为 sort 函数参数的字符串排序闭包能够改写为:闭包

reversed = sort(names) { $0 > $1 } 
当闭包很是长以致于不能在一行中进行书写时,Trailing 闭包就变得很是有用。 举例来讲,Swift 的  Array 类型有一个 map 方法,其获取一个闭包表达式做为其惟一参数。 数组中的每个元素调用一次该闭包函数,并返回该元素所映射的值(也能够是不一样类型的值)。 具体的映射方式和返回值类型由闭包来指定。
 
当提供给数组闭包函数后,map 方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。
 
下例介绍了如何在 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] 

 

上面的代码建立了整数数字到他们的英文名字之间映射字典。 同时定义了一个准备转换为字符串的整型数组。
 
您如今能够经过传递一个 trailing 闭包给 numbers 的 map 方法来建立对应的字符串版本数组。 须要注意的时调用 numbers.map 不须要在 map 后面包含任何括号,由于只须要传递闭包表达式这一个参数,而且该闭包表达式参数经过 trailing 方式进行撰写:
 
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 的类型,由于能够经过要映射的数组类型进行推断。
 
闭包 number 参数被声明为一个变量参数 ,所以能够在闭包函数体内对其进行修改。 闭包表达式制定了返回值类型为  String,以代表存储映射值的新数组类型为 String
 
 
闭包表达式在每次被调用的时候建立了一个字符串并返回。 其使用求余运算符 (number % 10) 计算最后一位数字并利用 digitNames 字典获取所映射的字符串。
注意: 
字典 digitNames 下标后跟着一个叹号 (!),由于字典下标返回一个可选值 (optional value),代表即便该 key不存在也不会查找失败。 在上例中,它保证了 number % 10 能够老是做为一个 digitNames 字典的有效下标 key。 所以叹号能够用于强展开 (force-unwrap) 存储在可选下标项中的 String 类型值。
 
 
从 digitNames 字典中获取的字符串被添加到输出的前部,逆序创建了一个字符串版本的数字。 (在表达式 number % 10中,若是number为16,则返回6,58返回8,510返回0)。
 
number 变量以后除以10。 由于其是整数,在计算过程当中未除尽部分被忽略。 所以 16变成了1,58变成了5,510变成了51。
 
整个过程重复进行,直到 number /= 10 为0,这时闭包会将字符串输出,而map函数则会将字符串添加到所映射的数组中。
 
上例中 trailing 闭包语法在函数后整洁封装了具体的闭包功能,而再也不须要将整个闭包包裹在 map 函数的括号内。
 
 
捕获 (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 类型的值。 关于函数返回其余函数的内容,请查看 Function Types as Return Types
 
makeIncrementor 函数定义了一个整型变量 runningTotal (初始为0) 用来存储当前增长总数。 该值经过 incrementor 返回。
 
makeIncrementor 有一个  Int 类型的参数,其外部命名为 forIncrement, 内部命名为 amount,表示每次 incrementor 被调用时 runningTotal 将要增长的量。
 
incrementor 函数用来执行实际的增长操做。 该函数简单地使 runningTotal 增长 amount,并将其返回。
 
若是咱们单独看这个函数,会发现看上去不一样寻常:
func incrementor() -> Int { runningTotal += amount return runningTotal } 

 

incrementor 函数并无获取任何参数,可是在函数体内访问了 runningTotal 和 amount 变量。这是由于其经过捕获在包含它的函数体内已经存在的 runningTotal 和 amount 变量而实现。
 
因为没有修改 amount 变量,incrementor 实际上捕获并存储了该变量的一个副本,而该副本随着 incrementor 一同被存储。
 
然而,由于每次调用该函数的时候都会修改 runningTotal 的值,incrementor 捕获了当前 runningTotal 变量的引用,而不是仅仅复制该变量的初始值。捕获一个引用保证了当 makeIncrementor 结束时候并不会消失,也保证了当下一次执行 incrementor 函数时,runningTotal 能够继续增长。
注意:
Swift 会决定捕获引用仍是拷贝值。 您不须要标注 amount 或者 runningTotal 来声明在嵌入的 incrementor 函数中的使用方式。 Swift 同时也处理 runingTotal 变量的内存管理操做,若是再也不被 incrementor 函数使用,则会被清除。
 
let incrementByTen = makeIncrementor(forIncrement: 10) 

 该例子定义了一个叫作 incrementByTen 的常量,该常量指向一个每次调用会加10的 incrementor 函数。 调用这个函数屡次能够获得如下结果:app

incrementByTen() // 返回的值为10  incrementByTen() // 返回的值为20  incrementByTen() // 返回的值为30 

 若是您建立了另外一个 incrementor,其会有一个属于本身的独立的 runningTotal 变量的引用。 下面的例子中,incrementBySevne 捕获了一个新的 runningTotal 变量,该变量和 incrementByTen 中捕获的变量没有任何联系:函数

 
let incrementBySeven = makeIncrementor(forIncrement: 7) incrementBySeven() // 返回的值为7  incrementByTen() // 返回的值为40 

 若是您闭包分配给一个类实例的属性,而且该闭包经过指向该实例或其成员来捕获了该实例,您将建立一个在闭包和实例间的强引用环。 Swift 使用捕获列表来打破这种强引用环优化

 
 
 
闭包是引用类型

上面的例子中,incrementBySeven 和 incrementByTen 是常量,可是这些常量指向的闭包仍然能够增长其捕获的变量值。 这是由于函数和闭包都是引用类型。
 
不管您将函数/闭包赋值给一个常量仍是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。 上面的例子中,incrementByTen 指向闭包的引用是一个常量,而并不是闭包内容自己。
 
这也意味着若是您将闭包赋值给了两个不一样的常量/变量,两个值都会指向同一个闭包:
let alsoIncrementByTen = incrementByTen alsoIncrementByTen() // 返回的值为50 
相关文章
相关标签/搜索