Swift4.2语言规范(九) 闭包
闭包是自包含的功能块,可以在代码中传递和使用。Swift中的闭包类似于C和Objective-C中的块以及其他编程语言中的lambdas。 闭包可以捕获和存储对定义它们的上下文中的任何常量和变量的引用。这被称为闭包那些常量和变量。Swift为您处理捕获的所有内存管理。 注意 如果您不熟悉捕获的概念,请不要担心。下面在捕获值中详细说明。 全局和嵌套函数,如捕获的功能,实际上是封闭的特殊情况。闭包采用以下三种形式之一:
Swift的闭包表达式具有干净,清晰的风格,优化可以在常见场景中鼓励简洁,无杂乱的语法。这些优化包括:
闭包表达式嵌套函数(在嵌套函数中引入)是一种方便的方法,可以将自包含的代码块命名和定义为更大函数的一部分。但是,在没有完整声明和名称的情况下编写类似函数的构造的更短版本有时是有用的。当您使用将函数作为其一个或多个参数的函数或方法时,尤其如此。 Closure表达式是一种以简短,集中的语法编写内联闭包的方法。Closure表达式提供了几种语法优化,用于以缩短的形式编写闭包,而不会丢失清晰度或意图。下面的闭包表达式示例通过 排序方法Swift的标准库提供了一个名为的方法 下面的闭包表达式示例使用该 let names = ["Chris","Alex","Ewa","Barry","Daniella"] 该 这个例子是对 提供排序闭包的一种方法是编写正确类型的普通函数,并将其作为参数传递给 1 func backward(_ s1: String,_ s2: String) -> Bool { 2 return s1 > s2 3 } 4 var reversedNames = names.sorted(by: backward) 5 // reversedNames is equal to ["Ewa","Daniella","Chris","Barry","Alex"] 如果第一个字符串( 然而,这是一种相当冗长的方式来编写本质上是单表达式函数。在此示例中,最好使用闭包表达式语法内联编写排序闭包。 闭包表达式语法Closure表达式语法具有以下一般形式: 1 { (parameters) -> return type in 2 statements 3 } 该参数在封闭表达式语法可以在输出参数,但是他们不能有一个默认值。如果命名variadic参数,则可以使用变量参数。元组也可以用作参数类型和返回类型。 下面的示例显示了 1 reversedNames = names.sorted(by: { (s1: String,s2: String) -> Bool in 2 return s1 > s2 3 }) 请注意,此内联闭包的参数声明和返回类型与 闭包的主体的开头由 因为封闭的主体很短,所以甚至可以写在一行上: reversedNames = names.sorted(by: { (s1: String,s2: String) -> Bool in return s1 > s2 } ) 这说明对 从上下文中推断类型因为排序闭包作为参数传递给方法,所以Swift可以推断出它的参数类型以及它返回的值的类型。该 reversedNames = names.sorted(by: { s1,s2 in return s1 > s2 } ) 在将闭包作为内联闭包表达式传递给函数或方法时,始终可以推断出参数类型和返回类型。因此,当闭包用作函数或方法参数时,您永远不需要以最完整的形式编写内联闭包。 尽管如此,如果您愿意,仍然可以使类型显式化,如果它避免了代码读者的歧义,则鼓励这样做。在该 单表达式闭包的隐式返回单表达式闭包可以通过 reversedNames = names.sorted(by: { s1,s2 in s1 > s2 } )
这里, 速记参数名称Swift自动提供速记参数名内联闭包,它可以使用的名称,指的是闭包的参数值 如果在闭包表达式中使用这些简写参数名称,则可以从其定义中省略闭包的参数列表,并且将从期望的函数类型推断缩写参数名称的数量和类型。的 reversedNames = names.sorted(by: { $0 > $1 } ) 在这里, 操作方法实际上有一种更短的方式来编写上面的闭包表达式。Swift的 reversedNames = names.sorted(by: { $0 > $1 } ) 欲了解更多有关操作方法,请参阅操作方法。 尾随闭包如果需要将闭包表达式作为函数的最终参数传递给函数,并且闭包表达式很长,则将其写为尾随闭包可能很有用。在函数调用的括号之后写入尾随闭包,即使它仍然是函数的参数。使用尾随闭包语法时,不要将闭包的参数标签写为函数调用的一部分。 1 func someFunctionThatTakesAClosure(closure: () -> Void) { 2 // function body goes here 3 } 4 5 // Here‘s how you call this function without using a trailing closure: 6 7 someFunctionThatTakesAClosure(closure: { 8 // closure‘s body goes here 9 }) 10 11 // Here‘s how you call this function with a trailing closure instead: 12 13 someFunctionThatTakesAClosure() { 14 // trailing closure‘s body goes here 15 } 上面的Closure Expression Syntax部分的字符串排序闭包可以 reversedNames = names.sorted() { $0 > $1 } 如果提供闭包表达式作为函数或方法的唯一参数,并且您将该表达式作为尾随闭包提供,则 reversedNames = names.sorted { $0 > $1 } 当闭包足够长以至于无法将其内联写入单行时,尾随闭包最有用。作为一个例子,Swift的 将提供的闭包应用于每个数组元素后,该 以下是如何使用 1 let digitNames = [ 2 0: "Zero",1: "One",2: "Two",3: "Three",4: "Four",3 5: "Five",6: "Six",7: "Seven",8: "Eight",9: "Nine" 4 ] 5 let numbers = [16,58,510] 上面的代码创建了整数位和其名称的英语版本之间的映射字典。它还定义了一个整数数组,可以转换为字符串。 您现在可以通过将闭包表达式作为尾随闭包传递给数组的方法来使用该 1 let strings = numbers.map { (number) -> String in 2 var number = number 3 var output = "" 4 repeat { 5 output = digitNames[number % 10]! + output 6 number /= 10 7 } while number > 0 8 return output 9 } 10 // strings is inferred to be of type [String] 11 // its value is ["OneSix","FiveEight","FiveOneZero"] 该 在此示例中, 闭包表达式构建一个 注意 对 从检索到的字符串
重复该过程直到 在上面的示例中使用尾随闭包语法在闭包支持的函数之后立即巧妙地封装了闭包的功能,而无需在 捕捉常量和变量闭包可以从定义它的周围上下文中捕获常量和变量。然后闭包可以引用并修改其体内的常量和变量的值,即使定义常量和变量的原始范围不再存在。 在Swift中,可以捕获值的最简单形式的闭包是嵌套函数,写在另一个函数体内。嵌套函数可以捕获其外部函数的任何参数,还可以捕获外部函数中定义的任何常量和变量。 这是一个调用函数的示例 1 func makeIncrementer(forIncrement amount: Int) -> () -> Int { 2 var runningTotal = 0 3 func incrementer() -> Int { 4 runningTotal += amount 5 return runningTotal 6 } 7 return incrementer 8 } 返回类型 该 该 在单独考虑时,嵌套 1 func incrementer() -> Int { 2 runningTotal += amount 3 return runningTotal 4 } 该 注意 作为优化,如果该值未被闭包变异,并且在创建闭包后该值未发生变化,则Swift可以改为捕获并存储值的副本。 Swift还处理在不再需要变量时处理变量所涉及的所有内存管理。 这是一个实际的例子 let incrementByTen = makeIncrementer(forIncrement: 10)
此示例设置一个常量 1 incrementByTen() 2 // returns a value of 10 3 incrementByTen() 4 // returns a value of 20 5 incrementByTen() 6 // returns a value of 30 如果您创建第二个增量器,它将拥有自己存储的对新的单独 1 let incrementBySeven = makeIncrementer(forIncrement: 7) 2 incrementBySeven() 3 // returns a value of 7
1 incrementByTen() 2 // returns a value of 40 注意 如果为类实例的属性分配闭包,并且闭包通过引用实例或其成员来捕获该实例,则将在闭包和实例之间创建一个强引用循环。Swift使用捕获列表来打破这些强大的参考周期。有关更多信息,请参阅闭包的强引用周期。 闭包是引用类型在上面的例子中, 无论何时将函数或闭包赋值给常量或变量,实际上都是将该常量或变量设置为对函数或闭包的引用。在上面的例子中,闭包的选择 这也意味着如果为两个不同的常量或变量分配闭包,那么这两个常量或变量都引用相同的闭包。 1 let alsoIncrementByTen = incrementByTen 2 alsoIncrementByTen() 3 // returns a value of 50 4 5 incrementByTen() 6 // returns a value of 60 上面的例子显示调用 离开闭包离开闭包是说离开当闭包作为参数传递给函数,但在函数返回之后被调用的函数。当声明一个以闭包作为其参数之一的函数时,可以 闭包可以转义的一种方法是存储在函数外部定义的变量中。作为示例,许多启动异步操作的函数将闭包参数作为完成处理程序。该函数在开始操作后返回,但是在操作完成之前不会调用闭包 - 闭包需要转义,以便稍后调用。例如: 1 var completionHandlers: [() -> Void] = [] 2 func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { 3 completionHandlers.append(completionHandler) 4 } 该 标记闭包 1 func someFunctionWithNonescapingClosure(closure: () -> Void) { 2 closure() 3 } 4 5 class SomeClass { 6 var x = 10 7 func doSomething() { 8 someFunctionWithEscapingClosure { self.x = 100 } 9 someFunctionWithNonescapingClosure { x = 200 } 10 } 11 } 12 13 let instance = SomeClass() 14 instance.doSomething() 15 print(instance.x) 16 // Prints "200" 17 18 completionHandlers.first?() 19 print(instance.x) 20 // Prints "100" 自动闭包一个autoclosure是自动创建来包装被真实作为参数传递给函数的表达式的闭包。它不接受任何参数,当它被调用时,它返回包含在其中的表达式的值。这种语法方便性允许您通过编写普通表达式而不是显式闭包来省略函数参数周围的大括号。 这是常见的来电称取autoclosures的功能,但它不是常见的实现那种功能。例如,该 autoclosure允许您延迟评估,因为在您调用闭包之前,内部代码不会运行。延迟评估对于具有副作用或计算成本高昂的代码非常有用,因为它可以让您控制何时评估该代码。下面的代码显示了闭包延迟评估的方式。 1 var customersInLine = ["Chris","Daniella"] 2 print(customersInLine.count) 3 // Prints "5" 4 5 let customerProvider = { customersInLine.remove(at: 0) } 6 print(customersInLine.count) 7 // Prints "5" 8 9 print("Now serving (customerProvider())!") 10 // Prints "Now serving Chris!" 11 print(customersInLine.count) 12 // Prints "4" 即使 当您将闭包作为参数传递给函数时,您会得到相同的延迟求值行为。 1 // customersInLine is ["Alex","Ewa","Daniella"] 2 func serve(customer customerProvider: () -> String) { 3 print("Now serving (customerProvider())!") 4 } 5 serve(customer: { customersInLine.remove(at: 0) } ) 6 // Prints "Now serving Alex!"
1 // customersInLine is ["Ewa","Daniella"] 2 func serve(customer customerProvider: @autoclosure () -> String) { 3 print("Now serving (customerProvider())!") 4 } 5 serve(customer: customersInLine.remove(at: 0)) 6 // Prints "Now serving Ewa!" 注意 过度使用autoclosures会使您的代码难以理解。上下文和函数名称应该明确表示正在推迟评估。 如果您想要允许转义的autoclosure,请使用 1 // customersInLine is ["Barry","Daniella"] 2 var customerProviders: [() -> String] = [] 3 func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) { 4 customerProviders.append(customerProvider) 5 } 6 collectCustomerProviders(customersInLine.remove(at: 0)) 7 collectCustomerProviders(customersInLine.remove(at: 0)) 8 9 print("Collected (customerProviders.count) closures.") 10 // Prints "Collected 2 closures." 11 for customerProvider in customerProviders { 12 print("Now serving (customerProvider())!") 13 } 14 // Prints "Now serving Barry!" 15 // Prints "Now serving Daniella!" 在上面的代码中,函数将闭包附加到数组,而不是调用作为 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |