官网链接
闭包是自包含的功能块,可以在代码中传递和使用。 Swift
中的闭包类似于C
和Objective-C
中的块以及其他编程语言中的lambdas
。
闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用,这也就是所谓的闭合并包裹那些常量和变量,因此被称为“闭包”,Swift
能够为你处理所有关于捕获的内存管理的操作。
函数中引入的全局函数和嵌套函数实际上是闭包的特例。 闭包采用以下三种形式之一:
• 全局函数是具有名称且不捕获任何值的闭包。
• 嵌套函数是具有名称的闭包,可以从其封闭函数中获取值。
• 闭包表达式是用轻量级语法编写的未命名闭包,可以从其周围的上下文捕获值。
Swift
的闭包表达式具有干净,清晰的风格,优化可以在常见场景中鼓励简洁,无杂乱的语法。 这些优化包括:
•利用上下文推断形式参数和返回值的类型;
•单表达式的闭包可以隐式返回;
•简写实际参数名;
•尾随闭包语法。
-
闭包表达式 (Closure Expressions)
Closure
表达式是一种以简短,集中的语法编写内联闭包的方法。 Closure
表达式提供了几种语法优化,用于以缩短的形式编写闭包,而不会丢失清晰度或意图。 下面的闭包表达式示例通过在几次迭代中细化sort(by :)
方法的单个示例来说明这些优化,每个迭代都以更简洁的方式表达相同的功能。
1.排序方法 (The Sorted Method)
Swift
的标准库提供了一个名为sorted(by :)
的方法,它根据您提供的排序闭包的输出对已知类型的值数组进行排序。 完成排序过程后,sorted(by :)
方法返回一个与旧数组相同类型和大小的新数组,其元素按正确的排序顺序排列。 原始数组不会被sorted(by :)
方法修改。
下面的闭包表达式示例使用sorted(by :)
方法按反向字母顺序对String
值数组进行排序。 这是要排序的初始数组:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sorted(by :)
方法接受一个闭包,它接受与数组内容相同类型的两个参数,并返回一个Bool
值,表示一旦值被排序,第一个值是出现在第二个值之前还是之后。 如果第一个值应出现在第二个值之前,则排序闭包需要返回true,否则返回false。
这个例子是对String
值的数组进行排序,因此排序闭包需要是type(String,String) - > Bool
的函数。
提供排序闭包的一种方法是编写正确类型的普通函数,并将其作为参数传递给sorted(by :)
方法:
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
然而,这是一种相当冗长的方式来编写基本上是单表达式函数(a> b)。 在此示例中,最好使用闭包表达式语法内联编写排序闭包。
2.闭包表达式语法 (Closure Expression Syntax)
{ (parameters) -> return type in
statements
}
闭包表达式语法中的参数可以是in-out
参数,但不能有默认值。如果命名可变参数,可以使用可变参数。元组还可以用作参数类型和返回类型。
下面的例子显示了来自上面的向后(_:_:)
函数的闭包表达式版本:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
闭包的主体的开头由in关键字引入。 这个关键字表示闭包的参数和返回类型的定义已经完成,闭包的主体即将开始。
3.从上下文中推断类型 (Inferring Type From Context)
因为排序闭包作为参数传递给方法,所以Swift
可以推断出它的参数类型以及它返回的值的类型。 sort(by :)
方法是在字符串数组上调用的,因此它的参数必须是type(String,String) - > Bool
的函数。 这意味着(String,String)
和Bool
类型不需要作为闭包表达式定义的一部分编写。 因为可以推断出所有类型,所以也可以省略返回箭头( - >)
和参数名称周围的括号:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
4.单语句表达式闭包的隐式返回 (Implicit Returns from Single-Expression Closures)
单语句表达式闭包可以通过从声明中省略return关键字来隐式返回单个表达式的结果,如上一个示例的此版本中所示:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
5.简写实际参数名 (Shorthand Argument Names)
Swift自动为内联闭包提供简写参数名称,可用于通过名称$ 0
,$ 1
,$ 2
等引用闭包参数的值。
reversedNames = names.sorted(by: { $0 > $1 } )
6.运算符方法 (Operator Methods)
实际上有一种更短的方式来编写上面的闭包表达式。Swift
的String
类型将其大于运算符(>)
的字符串特定实现定义为具有两个String
类型参数的方法,并返回Bool
类型的值。 这与sorted(by :)
方法所需的方法类型完全匹配。 因此,您可以简单地传入大于运算符,Swift
将推断您要使用其特定于字符串的实现:
reversedNames = names.sorted(by: >)
-
尾随闭包 (Trailing Closures)
如果需要将闭包表达式作为函数的最终参数传递给函数,并且闭包表达式很长,那么将其作为结尾闭包来编写可能会很有用。在函数调用的括号后面写一个尾随闭包,即使它仍然是函数的参数。在使用后面的闭包语法时,不要将闭包的参数标签作为函数调用的一部分。
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
// closure's body goes herr}
// Here's how you call this function with a trailing closure instead
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}
上面的Closure Expression Syntax
部分的字符串排序闭包可以作为尾随闭包写在sorted(by :)
方法的括号之外:
reversedNames = names.sorted() { $0 > $1 }
如果提供闭包表达式作为函数或方法的唯一参数,并且您将该表达式作为尾随闭包提供,则在调用函数时,不需要在函数或方法的名称后面写一对括号()
:
reversedNames = names.sorted { $0 > $1 }
以下是如何使用map(_ :)
方法和尾随闭包将Int值数组转换为String值数组。 数组[16,58,510]
用于创建新数组[“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]
let strings = numbers.map { (number) -> String in
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"]
-
捕捉值 (Capturing Values)
闭包可以从定义它的周围上下文中捕获常量和变量。 然后闭包可以引用并修改其体内的常量和变量的值,即使定义常量和变量的原始范围不再存在。
在Swift
中,可以捕获值的最简单形式的闭包是嵌套函数,写在另一个函数体内。 嵌套函数可以捕获其外部函数的任何参数,还可以捕获外部函数中定义的任何常量和变量。
这是一个名为makeIncrementer
的函数示例,它包含一个名为incrementmenter
的嵌套函数。 嵌套的incrementmenter()
函数从其周围的上下文中捕获两个值runningTotal
和amount
。 捕获这些值后,makeIncrementer
会将增量器作为一个闭包返回,每次调用时,会增加runningTotal
的数量。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
makeIncrementer
的返回类型是() - > Int
。 这意味着它返回一个函数,而不是一个简单的值。 它返回的函数没有参数,每次调用时返回一个Int
值。
NOTE
作为优化,如果该值未被闭包变异,并且在创建闭包后该值未发生变化,则Swift
可以改为捕获并存储值的副本。
Swift
还处理在不再需要变量时处理变量所涉及的所有内存管理。
这是makeIncrementer
的一个例子:
let incrementByTen = makeIncrementer(forIncrement: 10)
这个例子设置了一个名为incrementByTen
的常量来引用一个incrementer
函数,每次调用该函数时,该函数都会将10
添加到它的runningTotal
变量中。多次调用函数的结果如下:
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30
-
闭包是引用类型 (Closures Are Reference Types)
在上面的示例中,incrementBySeven和incrementByTen是常量,但这些常量引用的闭包仍然能够增加它们捕获的runningTotal变量。 这是因为函数和闭包是引用类型。
无论何时将函数或闭包赋值给常量或变量,实际上都是将该常量或变量设置为对函数或闭包的引用。 在上面的例子中,incrementByTen引用的闭包选择是常量,而不是闭包本身的内容。
这也意味着如果为两个不同的常量或变量分配闭包,那么这两个常量或变量都引用相同的闭包。
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50
incrementByTen()
// returns a value of 60
-
逃逸闭包 (Escaping Closures)
当闭包作为参数传递给函数时,闭包被称为转义函数,但在函数返回后调用。 当您声明一个以闭包作为其参数之一的函数时,您可以在参数的类型之前编写@escaping
以指示允许闭包转义。
闭包可以转义的一种方法是存储在函数外部定义的变量中。 作为示例,许多启动异步操作的函数将闭包参数作为完成处理程序。 该函数在开始操作后返回,但是在操作完成之前不会调用闭包 - 闭包需要转义,以便稍后调用。 例如:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_ :)
函数将一个闭包作为其参数,并将其添加到在函数外声明的数组中。 如果没有使用@escaping
标记此函数的参数,则会出现编译时错误。
相当于Objective-C中的__block
关键字。
用@escaping
标记闭包意味着你必须在闭包中明确引用self
。 例如,在下面的代码中,传递给someFunctionWithEscapingClosure(_ :)
的闭包是一个转义闭包,这意味着它需要显式引用self
。 相反,传递给someFunctionWithNonescapingClosure(_ :)
的闭包是一个非逃避闭包,这意味着它可以隐式引用自身。
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100"
-
自动闭包 (Autoclosures)
autoclosure
是一个自动创建的闭包,用于包装作为参数传递给函数的表达式。 它不接受任何参数,当它被调用时,它返回包含在其中的表达式的值。 这种语法方便性允许您通过编写普通表达式而不是显式闭包来省略函数参数周围的大括号。
调用带有自动闭包的函数是很常见的,但是实现这种函数是不常见的。例如,assert(condition:message:file:line:)
函数的条件和消息参数采用自动闭包;它的条件参数仅在调试构建中求值,其消息参数仅在条件为假的情况下求值。
自动闭包允许你延迟处理,因此闭包内部的代码直到你调用它的时候才会运行。对于有副作用或者占用资源的代码来说很有用,因为它可以允许你控制代码何时才进行求值。下面的代码展示了闭包如何延迟求值。
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"
即使customersInLine
数组的第一个元素被闭包内的代码删除,数组元素也不会被删除,直到实际调用闭包。如果从未调用过闭包,则永远不会计算闭包内的表达式,这意味着永远不会删除数组元素。请注意,customerProvider
的类型不是String
,而是()-> String
一个没有返回字符串的参数的函数。
当您将闭包作为参数传递给函数时,您会得到相同的延迟求值行为。
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"
上面列表中的serve(customer :)
函数采用显式闭包,返回客户的名称。 下面的serve(customer :)
版本执行相同的操作,但不是采用显式闭包,而是通过使用@autoclosure
属性标记其参数的类型来进行autoclosure
。 现在你可以像调用String
参数而不是闭包那样调用函数。 参数自动转换为闭包,因为customerProvider
参数的类型标有@autoclosure
属性。
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"
NOTE
过度使用autoclosures
会使您的代码难以理解。 上下文和函数名称应该明确表示正在推迟评估。
如果您想要允许转义的autoclosure
,请同时使用@autoclosure
和@escaping
属性。 上面的Escaping Closures
中描述了@escaping
属性。
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
在上面的代码中,不是调用作为customerProvider
参数传递给它的闭包,而是collectCustomerProviders(_ :)
函数将闭包附加到customerProviders
数组。 数组声明在函数范围之外,这意味着数组中的闭包可以在函数返回后执行。 因此,必须允许customerProvider
参数的值转义函数的作用域。