- 广义上函数和内嵌函数都属于特殊的闭包
- 闭包的三种格式
1-全局函数是有名字,但捕获值的闭包
2-内嵌函数是有名字,可以捕获函数内变量的闭包
3-闭包表达式没有名字,使用清凉的语法捕获上下文中的变量。 - 完整格式
{ (parameters) -> return type in statements }
- 具体写法
func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2 } var reversedNames = names.sorted(by: backward) reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 }) reversedNames = names.sorted(by: { (s1, s2) -> Bool in return s1 > s2 }) reversedNames = names.sorted(by: {s1, s2 in return s1 > s2 }) reversedNames = names.sorted(by: { s1, s2 in s1 > s2 }) reversedNames = names.sorted(by: { $0 > $1 }) reversedNames = names.sorted(by: >)
sorted()内以一个bool值表示s1是否在s2前面。s1>s2表示当s1大于s2时s1在前面,即递减排列。
- trailing闭包可以使闭包在函数括号后的大括号中,当函数不需要其他参数时括号也可以省略
reversedNames = names.sorted() { $0 > $1 } reversedNames = names.sorted { $0 > $1 }
- 闭包的具体使用
let strings = numbers.map { (number) -> String in //作为参数传入的number为constant,不能修改,需要赋给新定义的var类型的number才可以修改 var number = number var output = "" repeat { output = digitNames[number % 10]! + output number /= 10 } while number > 0 return output } // strings is inferred to be of type [String] // its value is ["OneSix", "FiveEight", "FiveOneZero"]
- 闭包如内嵌函数时,可以捕获内嵌函数外,外部函数内的变量,并且作为闭包中的单独变量,获取不同闭包时得到不同变量。
- 函数引用,不加括号的函数名代表函数的引用类型,加括号代表函数内容本身即返回值
- 逃逸闭包用作异步,例如网络回调,使用@escaping关键字,对此函数的调用时传入闭包内容(大括号内代码),函数体内会将此闭包传给completionHandlers数组的第一项,直到数组第一项执行时闭包内容才会被真正执行
var completionHandlers: [() -> Void] = [] func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void ) { completionHandlers.append(completionHandler) } someFunctionWithEscapingClosure { self.x = 100 } completionHandlers.first?()
- 闭包可以作为主函数的一个参数传入,在主函数体内使用参数名调用,在调用主函数的同时将此闭包(实际就是一个匿名函数)实现并作为参数传入
func serve(custom cusomerProvider: () -> String) { print("Now serving \(cusomerProvider())!") } serve(custom: { customersInLine.remove(at: 0) })
- 所谓自动闭包就是当不包含参数的闭包作为函数参数传入时可以使用自动闭包关键字@autoclosure。这样在调用时不需要使用大括号指定闭包,而是直接指定闭包内容即可,如下
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] func serve(customer customerProvider: () -> String) { //闭包调用位置 print("Now serving \(customerProvider())!") } //customersInLine.remove(at: 0)作为闭包内容代码传入 serve(customer: { customersInLine.remove(at: 0) } ) // Prints "Now serving Alex!" // 区别上面的非自动闭包,使用@autoclosure定义为自动闭包,从而在调用传入闭包内容的时候省略了大括号 func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } serve(customer: customersInLine.remove(at: 0))
实际上区别于普通闭包(非自动闭包)作为函数参数,在调用时实际上需要传入闭包(也就是函数体),需要使用
{}
。而自动闭包不需要特别的传入闭包(也就是函数体),也就是不需要使用{}
。只需要像传递普通参数例如String
等类型参数一样,自动闭包会自动将传入内容转换为闭包。 - 自动闭包可以延迟执行想要执行的代码
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] print(customersInLine.count) // Prints "5" //只是定义, 并没有真正的执行 let customerProvider = { customersInLine.remove(at: 0) } print(customersInLine.count) // Prints "5" //在这里真正执行 print("Now serving \(customerProvider())!") // Prints "Now serving Chris!" print(customersInLine.count) // Prints "4"
总结
本章的闭包含义其实就是相对简单的匿名函数,要注意传入闭包作为参数时实际上需要传入的直接为闭包函数的实现内容(函数体)。
而函数收到闭包参数时,可以选择在函数内执行闭包,也可以选择将闭包引用传递给其他变量(这就是非逃逸闭包和逃逸闭包(@escaping)的区别),逃逸闭包只有当真正调用闭包时才会执行闭包内部的内容。
不使用自动闭包关键字时传入闭包作为参数需要大括号体现完整的闭包(函数体)特征,而使用自动闭包关键字(@autoclosure)时则不需大括号。
闭包作为函数返回值时,首先传入函数参数获取对应闭包,在调用闭包时执行闭包内代码,闭包内可以捕获一些闭包定义时上下文中的一些变量。
实例
//Example for Escaping Closures and Autoclosures
//定义一个用于储存某种函数(实际上是闭包)的数组
var customerProviders: [() -> String] = []
//定义某个以自动逃逸闭包作为参数,闭包无参数返回string类型,函数本身无返回值
func collectCusromerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
//@escaping关键字,闭包没有直接实现,而是传递给数组保存
customerProviders.append(customerProvider)
}
//由于使用@autoclosure关键字,否则collectCusromerProviders({customersInLine.remove(at: 0)})
//执行函数,并将闭包customersInLine.remove(at: 0)作为参数传入
collectCusromerProviders(customersInLine.remove(at: 0))
collectCusromerProviders(customersInLine.remove(at: 0))
//两个闭包保存在数组中
print("Collected \(customerProviders.count) clousers.")
//遍历闭包数组,执行数组中闭包
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
网友评论