尾随闭包
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,将这个闭包替换成为尾随闭包的形式很有用。尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
funcsomeFunctionThatTakesAClosure(closure: () ->Void) { // 函数体部分 } // 以下是不使用尾随闭包进行函数调用 someFunctionThatTakesAClosure(closure: { // 闭包主体部分 }) // 以下是使用尾随闭包进行函数调用 someFunctionThatTakesAClosure() { // 闭包主体部分 }
在 闭包表达式语法 上章节中的字符串排序闭包可以作为尾随包的形式改写在 sorted(by:)
方法圆括号的外面:
reversedNames = names.sorted() { $0 > $1 }
如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 ()
省略掉:
reversedNames = names.sorted { $0 > $1 }
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。举例来说,Swift 的 Array
类型有一个 map(_:)
方法,这个方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。
当提供给数组的闭包应用于每个数组元素后,map(_:)
方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。
下例介绍了如何在 map(_:)
方法中使用尾随闭包将 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]
如上代码创建了一个整型数位和它们英文版本名字相映射的字典。同时还定义了一个准备转换为字符串数组的整型数组。
你现在可以通过传递一个尾随闭包给 numbers
数组的 map(_:)
方法来创建对应的字符串版本数组:
let strings = numbers.map { (number) ->Stringin var number = number var output ="" repeat { output = digitNames[number %10]!+ output number /=10 } while number >0 return output } // strings 常量被推断为字符串类型数组,即 [String] // 其值为 ["OneSix", "FiveEight", "FiveOneZero"]
map(_:)
为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数 number
的类型,因为可以通过要映射的数组类型进行推断。
在该例中,局部变量 number
的值由闭包中的 number
参数获得,因此可以在闭包函数体内对其进行修改,(闭包或者函数的参数总是常量),闭包表达式指定了返回类型为 String
,以表明存储映射值的新数组类型为 String
。
闭包表达式在每次被调用的时候创建了一个叫做 output
的字符串并返回。其使用求余运算符(number % 10
)计算最后一位数字并利用 digitNames
字典获取所映射的字符串。这个闭包能够用于创建任意正整数的字符串表示。
注意
字典
digitNames
下标后跟着一个叹号(!
),因为字典下标返回一个可选值(optional value),表明该键不存在时会查找失败。在上例中,由于可以确定number % 10
总是digitNames
字典的有效下标,因此叹号可以用于强制解包(force-unwrap)存储在下标的可选类型的返回值中的String
类型的值。
从 digitNames
字典中获取的字符串被添加到 output
的 前部 ,逆序建立了一个字符串版本的数字。(在表达式 number % 10
中,如果 number
为 16
,则返回 6
,58
返回 8
,510
返回 0
。)
number
变量之后除以 10
。因为其是整数,在计算过程中未除尽部分被忽略。因此 16
变成了 1
,58
变成了 5
,510
变成了 51
。
整个过程重复进行,直到 number /= 10
为 0
,这时闭包会将字符串 output
返回,而 map(_:)
方法则会将字符串添加到映射数组中。
在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 map(_:)
方法的括号内。
如果一个函数接受多个闭包,您需要省略第一个尾随闭包的参数标签,并为其余尾随闭包添加标签。例如,以下函数将为图片库加载一张图片:
funcloadPicture(fromserver: Server, completion:(Picture) ->Void, onFailure: () ->Void) { iflet picture =download("photo.jpg", from: server){ completion(picture) }else{ onFailure() } }
当您调用该函数以加载图片时,需要提供两个闭包。第一个闭包是一个完成处理程序,它在成功下载后加载图片;第二个闭包是一个错误处理程序,它向用户显示错误。
loadPicture(from: someServer){ picture in someView.currentPicture = picture } onFailure: { print("Couldn't download the next picture.") }
在本例中,loadPicture(from:completion:onFailure:)
函数将它的网络任务分配到后台,并在网络任务完成时调用两个完成处理程序中的一个。通过这种方法编写函数,您将能够把负责处理网络故障的代码和成功下载后更新用户界面的代码干净地区分开,而不是只使用一个闭包处理两种情况。
注意
完成处理程序可能很难阅读,特别是您必须嵌套多个完成处理程序时。另一种方法是使用异步代码,如章节并发 中所述。