Swift闭包(Closures)

最后更新于:2022-04-01 14:34:07

闭包是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。 在Swift中的闭包与C、OC中的blocks和其它编程语言(如Python)中的lambdas类似。 闭包可以捕获和存储上下文中定义的的任何常量和变量的引用。这就是所谓的变量和变量的自封闭, 因此命名为”闭包“(“Closures)”)。Swift还会处理所有捕获的引用的内存管理。 全局函数和嵌套函数其实就是特殊的闭包。 闭包的形式有三种: - 全局函数都是闭包,有名字但不能捕获任何值。 - 嵌套函数都是闭包,且有名字,也能捕获封闭函数内的值。 - 闭包表达式都是无名闭包,使用轻量级语法,可以根据上下文环境捕获值。 Swift中的闭包有很多优化的地方: - 根据上下文推断参数和返回值类型; - 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略return); - 可以使用简化参数名,如0,1(从0开始,表示第i个参数…); - 提供了尾随闭包语法(Trailing closure syntax)。 ## 闭包表达式 嵌套函数是非常强大的功能,在一个函数体内嵌套另一个函数。将函数作为参数和返回值也非常有用。这些都是一些特殊情况下的闭包。 闭包表达式是一种简短的、集中的语法。闭包表达式为了缩短代码以及优化代码的阅读性,提供了几种语法优化。这里使用数组的排序为大家展示闭包的优化。 ### Sort方法 ~~~ // 函数做参数,排序 let names = ["阳君", "937447974", "a", "b", "c"] func backwards(s1: String, _ s2: String) -> Bool { return s1 > s2 } var reversed = names.sort(backwards) ~~~ 从代码中可以看出,将函数作为参数传递对于代码的阅读性不是很好,这里就需要闭包表达式对其优化。 ### 闭包语法 闭包表达式的结构图如下所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-01_574e9af8cd710.jpg "") - parameters:闭包接受的参数; - return type:闭包运行完毕的返回值; - statements:闭包内的运行代码。 下面运用闭包表达式代替backwards函数对sort进行优化。 ~~~ reversed = names.sort({ (s1: String, s2: String) -> Bool in return s1 > s2 }) ~~~ 当要运行的代码很少时,你也可以将它们写在一行。 ~~~ reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } ) ~~~ ### 通过上下文判断类型 在闭包中,我们不必写参数的类型和返回值的类型,闭包可以通过上下文自动判断参数类型和返回值类型。 ~~~ reversed = names.sort( { s1, s2 in return s1 > s2 } ) ~~~ ### 从单一表达式隐藏Return 在闭包中,如果运行的内容很少只有一行,则不必写return,闭包会自动返回。 ~~~ reversed = names.sort( { s1, s2 in s1 > s2 } ) ~~~ ### 速记参数名称 在闭包中,我们不必命名参数名称。闭包中的参数可使用`$`去获得,第一个参数为`$0`,第二个为`$1`。 ~~~ reversed = names.sort( { $0 > $1 } ) ~~~ ### 算子函数 当在闭包中,只有一个表达式,做操作。如在sort中,只有两个参数做比较操作。闭包支持只输入>或<做比较。 ~~~ reversed = names.sort(>) ~~~ ## 尾随闭包 如果函数需要一个闭包作为参数,且这个参数是最后一个参数。我们又不想在()内写太多代码,我们可以运用尾随闭包。尾随闭包意味着闭包可以放在函数参数列表外,也就是括号外。 ~~~ var reversed = names.sort() { $0 > $1 } ~~~ 当尾随闭包中的参数只有一个时,我们可以省略()。 ~~~ reversed = names.sort { $0 > $1 } ~~~ ## 捕获值 闭包可以根据上下文环境捕获到定义的常量和变量。闭包可以引用和修改这些捕获到的常量和变量,就算在原来的范围内定义为常量或者变量已经不再存在。在Swift中闭包的最简单形式是嵌套函数。 ~~~ func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer } let incrementByTen = makeIncrementer(forIncrement: 10) print("\(incrementByTen())") // prints "10" print("\(incrementByTen())") // prints "20" print("\(incrementByTen())") // prints "30" ~~~ 上面的例子介绍了,有个函数makeIncrementer,在函数内有一个嵌套函数incremented。嵌套函数可以通过上下文使用它的外部值runningTotal和amount。 当你想声明另一个闭包类型时,可以像声明属性一样声明。 ~~~ let incrementBySeven = makeIncrementer(forIncrement: 7) print("\(incrementBySeven())") // prints "7" print("\(incrementByTen())") // prints "40" ~~~ 可以看出两个闭包的引用是完全独立工作的。 ## 闭包是引用类型 运用属性执行的闭包后,我们可以用另一个属性去引用,对于两个属性来说,它们是完全相同的,指向同一个闭包。 ~~~ let alsoIncrementByTen = incrementByTen alsoIncrementByTen() // prints "50" ~~~ ## Noescape关键字 @noescape主要用于解决“保留环”问题如下所示,当你调用someFunctionWithEscapingClosure函数时,使用全局属性,会使用了self,这样你会发现每次调用闭包时,都会使用捕获self,这样容易造成内存泄露的问题,而且闭包中的操作其实是一成不变的,没有必要每次都访问。 Swift鉴于这种情况,希望在闭包内不使用self,因此产生了@noescape关键字。将@noescape写入闭包名前。这样在编写闭包内代码时,无须使用self属性,也避免了保留环问题。 ~~~ var completionHandlers: [() -> Void] = [] func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) { closure() // completionHandlers.append(closure) //会报错,closure无法被保存 } func someFunctionWithEscapingClosure(completionHandler: () -> Void) { completionHandler() completionHandlers.append(completionHandler) } class SomeClass { var x = 10 func doSomething() { // 内存溢出,保留环问题 someFunctionWithEscapingClosure { self.x = 100 } someFunctionWithNoescapeClosure { x = 200 } } } let instance = SomeClass() instance.doSomething() print(instance.x) // prints "200" completionHandlers.first?() print(instance.x) // prints "100" ~~~ ## Autoclosures关键字 在闭包中,我们调用函数时,是将代码封装为一个闭包传递给函数。如下所示: ~~~ var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] func serveCustomer(customerProvider: () -> String) { print("Now serving \(customerProvider())!") } serveCustomer( { customersInLine.removeAtIndex(0) } ) // prints "Now serving Alex!" ~~~ 此时我们会思考,能否让代码直接为参数传递过去,也就是不用{}包含代码。@autoclosure关键字可以帮助你完成这种机制。 ~~~ // customersInLine is ["Ewa", "Barry", "Daniella"] func serveCustomer2(@autoclosure customerProvider: () -> String) { print("Now serving \(customerProvider())!") } // 闭包作为参数 serveCustomer2(customersInLine.removeAtIndex(0)) // prints "Now serving Ewa!" ~~~ 使用了@autoclosure默认也是使用@noescape,如果你只想使用autoclosure的特性,不想使用noescape的特性,你可以使用escape关键字,如下所示: ~~~ // customersInLine is ["Barry", "Daniella"] var customerProviders: [() -> String] = [] //autoclosure和escaping一起用 func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) { customerProviders.append(customerProvider) } // 添加闭包,并且闭包此时为参数 collectCustomerProviders(customersInLine.removeAtIndex(0)) collectCustomerProviders(customersInLine.removeAtIndex(0)) //循环使用闭包 for customerProvider in customerProviders { print("Now serving \(customerProvider())!") } // prints "Now serving Barry!" // prints "Now serving Daniella!" ~~~ ## 其他 ### 参考资料 [The Swift Programming Language (Swift 2.1)](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html) ### 文档修改记录 | 时间 | 描述 | |-----|-----| | 2015-10-28 | 根据 [The Swift Programming Language (Swift 2.1)](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html)中的Closures总结 | 版权所有:[http://blog.csdn.net/y550918116j](http://blog.csdn.net/y550918116j)
';