这是用户在 2025-7-14 17:18 为 https://app.immersivetranslate.com/markdown/ 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

闭包

将一起执行的代码块封装在一起,而无需创建命名函数。

闭包是包含完整功能的独立块,可以传递并在代码中使用。在 Swift 中,闭包类似于其他编程语言中的匿名函数、lambda 表达式和代码块。

闭包可以捕获并存储从其定义上下文中获取的任何常量和变量的引用。这被称为 捕获 这些常量和变量。Swift 会为你处理所有捕获的内存管理。

注意:如果你不熟悉捕获的概念,不要担心。它会在 doc:Closures#Capturing-Values 中详细解释。

全局函数和嵌套函数(如 doc:Functions 中介绍的),实际上是闭包的特殊形式。闭包有三种形式:

  • 全局函数是具有名称且不捕获任何值的闭包。
  • 嵌套函数是有名称且可以从其外部函数捕获值的闭包。
  • 闭包表达式是用轻量级语法编写的无名闭包,可以捕获其周围上下文中的值。

Swift 的闭包表达式具有简洁清晰的风格,并且在常见场景中通过优化鼓励使用简洁、无杂乱的语法。这些优化包括:

  • 从上下文推断参数和返回值类型
  • 单表达式闭包的隐式返回
  • 简写参数名
  • 尾随闭包语法

闭包表达式

嵌套函数,如在 doc:Functions#Nested-Functions 中介绍的,是一种方便的方法,用于在较大函数中命名和定义自包含的代码块。然而,在处理接受一个或多个函数作为参数的函数或方法时,有时编写没有完整声明和名称的函数样式的简短版本会很有用。

闭包表达式是一种简洁、聚焦的语法,用于编写内联闭包。闭包表达式提供了几种语法优化,可以在不损失清晰度或意图的情况下,以更简洁的形式编写闭包。下面的闭包表达式示例通过多次迭代,逐步精炼一个使用 sorted(by:) 方法对字符串数组进行逆序排序的例子,每次迭代都以更简洁的方式表达相同的功能。

排序方法

Swift 标准库提供了一个名为 sorted(by:) 的方法,该方法根据您提供的排序闭包的输出,对已知类型的值数组进行排序。完成排序过程后,sorted(by:) 方法返回一个新数组,该数组与旧数组类型相同且大小相等,其元素按正确的排序顺序排列。sorted(by:) 方法不会修改原始数组。

下面的闭包表达式示例使用 sorted(by:) 方法对字符串数组进行逆序排序。这是要排序的初始数组:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

`sorted(by:)` 方法接受一个闭包,该闭包接受两个与数组内容相同类型的参数,并返回一个 `Bool` 值,表示在排序后第一个值是否应出现在第二个值之前。排序闭包需要返回 `true`,如果第一个值应出现在第二个值之前,否则返回 `false`。 例如,如果第一个值应出现在第二个值之前,返回 `true`,否则返回 `false`。

这个例子中,数组包含 `String` 类型的值,因此排序闭包需要是一个类型为 `(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"]

如果第一个字符串(s1)大于第二个字符串(s2),则 backward(_:_:) 函数将返回 true,表示 s1 应该在排序数组中出现在 s2 之前。对于字符串中的字符,“大于”意味着“在字母表中出现得更晚”。这意味着字母 "B"“大于”字母 "A",字符串 "Tom" 大于字符串 "Tim"。这将导致逆字母排序,例如 "Barry" 会出现在 "Alex" 之前,依此类推。

然而,这实际上是一种冗长的写法,用于表示一个单表达式函数(a > b)。在这个例子中,最好直接使用闭包表达式语法来编写排序闭包。

闭包表达式语法

闭包表达式语法具有以下一般形式:

{ (<#parameters#>) -> <#return type#> in
   <#statements#>
}

闭包表达式的参数可以是 inout 参数,但它们不能有默认值。如果命名了可变参数,则可以使用可变参数。元组也可以用作参数类型和返回类型。

下面的例子展示了上述 backward(_:_:) 函数的闭包表达式版本:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

请注意,内联闭包中参数和返回类型的声明与 backward(_:_:) 函数的声明完全相同。在两种情况下,都是以 (s1: String, s2: String) -> Bool 的形式书写。然而,对于内联闭包表达式,参数和返回类型是在大括号内书写,而不是在大括号外。

闭包主体的开始由 in 关键字引入。这个关键字表示闭包参数和返回类型的定义已经结束,闭包的主体即将开始。

因为闭包的主体非常短,甚至可以写在一行上:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

这说明对 sorted(by:) 方法的整体调用保持不变。整个方法参数仍然用一对括号包裹。不过,这个参数现在是一个内联闭包。

根据上下文推断类型

由于排序闭包作为方法的参数传递,Swift 可以推断其参数类型和返回值类型。sorted(by:) 方法被应用于字符串数组,因此其参数必须是类型为 (String, String) -> Bool 的函数。这意味着闭包表达式的定义中不需要写出 (String, String)Bool 类型。由于所有类型都可以被推断出来,返回箭头(->)以及参数名称周围的括号也可以省略:

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

当将闭包作为内联闭包表达式传递给函数或方法时,总是可以推断出参数类型和返回类型。因此,当闭包作为函数或方法参数使用时,你无需写出闭包的完整形式。

不过,如果你愿意,仍然可以显式地写出类型,这样做可以避免代码读者产生歧义。在`sorted(by:)`方法的情况下,从排序正在进行这一事实可以看出闭包的目的,读者可以安全地假设闭包很可能在处理`String`值,因为它正在帮助对字符串数组进行排序。

单表达式闭包的隐式返回

单表达式闭包可以通过省略`return`关键字来隐式返回其单表达式的结果,如下一个示例版本所示:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

在这里,`sorted(by:)` 方法参数的函数类型表明闭包必须返回一个 `Bool` 值。由于闭包的主体包含一个单一表达式(`s1 > s2`),该表达式返回一个 `Bool` 值,因此没有歧义,可以省略 `return` 关键字。

简短参数名

Swift 会自动为内联闭包提供简短参数名,可以在闭包表达式中通过 `$0`、`$1`、`$2` 等名称引用闭包参数的值。

如果你在闭包表达式中使用这些简短参数名,可以省略闭包的参数列表。简短参数名的类型会从预期的函数类型中推断出来,你使用最高编号的简短参数名决定了闭包接受的参数数量。由于闭包表达式完全由其主体组成,因此也可以省略 `in` 关键字。

reversedNames = names.sorted(by: { $0 > $1 } )

Here, $0 and $1 refer to the closure's first and second String arguments. Because $1 is the shorthand argument with highest number, the closure is understood to take two arguments. Because the sorted(by:) function here expects a closure whose arguments are both strings, the shorthand arguments $0 and $1 are both of type String.

运算符方法

实际上,上面的闭包表达式还可以更简洁地写。Swift 的 String 类型定义了其特定于字符串的大于运算符 (>>) 的实现,该实现是一个具有两个 String 类型参数的方法,返回值类型为 Bool。这正好符合 sorted(by:) 方法所需的方法类型。因此,你可以直接传递大于运算符,Swift 会推断出你想要使用其特定于字符串的实现:

reversedNames = names.sorted(by: >)

有关运算符方法的更多信息,请参见 doc:AdvancedOperators#Operator-Methods

闭包尾随语法

如果需要将闭包表达式作为函数的最后一个参数传递给函数,并且闭包表达式较长,那么将其写为闭包尾随语法可能会更有用。你可以在函数调用的括号后面编写闭包尾随语法,尽管闭包尾随语法仍然是函数的一个参数。使用闭包尾随语法时,不需要在函数调用中写入闭包的第一个参数标签。一个函数调用可以包含多个闭包尾随语法;然而,下面的前几个示例仅使用了一个闭包尾随语法。

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 here
})

// Here's how you call this function with a trailing closure instead:

someFunctionThatTakesAClosure() {
    // trailing closure's body goes here
}

上面在“doc:Closures#Closure-Expression-Syntax”部分给出的字符串排序闭包可以写在 `sorted(by:)` 方法括号之外作为闭包尾随语法:

reversedNames = names.sorted() { $0 > $1 }

如果闭包表达式是函数或方法的唯一参数,并且你将该表达式作为闭包尾随语法提供,那么不需要写入一对括号 `()`。 在调用函数时函数或方法名称之后:

reversedNames = names.sorted { $0 > $1 }

尾随闭包在闭包足够长以至于无法在一行内写入时最为有用。例如,Swift 的`Array`类型有一个名为`map(_:)`的方法,该方法接受一个闭包表达式作为其唯一参数。该闭包对数组中的每个项调用一次,并返回该项的另一种映射值(可能是其他类型)。您通过在传递给`map(_:)`的方法中编写的闭包代码中指定映射的性质和返回值的类型来指定映射的性质和返回值的类型。

在对每个数组元素应用提供的闭包之后,`map(_:)`方法返回一个新的数组,其中包含所有新的映射值,顺序与原始数组中相应值的顺序相同。

以下是如何使用尾随闭包和`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]

上述代码创建了一个字典,用于映射整数位及其对应的英文名称。它还定义了一个整数数组,准备将其转换为字符串数组。

现在你可以使用 `numbers` 数组通过向数组的 `map(_:)` 方法传递闭包表达式(作为尾随闭包)来创建一个 `String` 值数组:

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"]

`map(_:)` 方法会为数组中的每个项调用一次闭包表达式。你不需要指定闭包输入参数 `number` 的类型,因为类型可以从要映射的数组中的值中推断出来。

在这个例子中,变量 `number` 被初始化为闭包的 `number` 参数的值,以便可以在闭包体内修改该值。(函数和闭包的参数始终是常量。)闭包表达式还指定了返回类型为 `String`,以表示将存储在映射输出数组中的类型。

每次调用闭包表达式时,它都会构建一个名为 `output` 的字符串。它通过使用取余运算符 (`number % 10`) 计算 `number` 的最后一位数字,并使用该数字在 `digitNames` 字典中查找合适的字符串。该闭包可以用来生成任意正整数的字符串表示。

注意:对 `digitNames` 字典下标调用后跟了一个感叹号 (`!`),因为字典下标返回一个可选值,表示如果键不存在则字典查找可能会失败。在上面的例子中,可以保证 `number % 10` will always be a valid subscript key for the digitNames 字典,并且使用感叹号来强制解包 subscript 的可选返回值中存储的 String 值。

digitNames 字典中检索到的字符串被添加到 output 代码的前面 ,从而有效地构建了一个数字的逆序字符串。(表达式 number % 10 给出的值为 6 for 16, 8 for 58, and 0 for 510.)

变量 number 然后被除以 10。由于它是整数,在除法过程中会被向下取整,所以 16 变为 1,58 变为 5,510 变为 51。

该过程重复执行,直到 `number` 等于 `0`,此时闭包的 `output` 字符串通过闭包返回,并由 `map(_:)` 方法添加到输出数组中。

上述示例中使用尾随闭包语法巧妙地将闭包的功能封装在支持该闭包的函数之后,无需将整个闭包嵌套在 `map(_:)` 方法的外层括号中。

如果一个函数接受多个闭包,你可以省略第一个尾随闭包的参数标签,并为剩余的尾随闭包标注参数标签。例如,下面的函数用于加载照片画廊中的图片:

func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
    if let 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:) 函数将网络任务分派到后台,并在网络任务完成后调用其中一个完成处理程序。这样写函数可以让代码清晰地分离出处理网络失败的部分和下载成功后更新用户界面的部分,而不是使用一个同时处理这两种情况的闭包。

注意:当需要嵌套多个处理程序时,完成处理程序可能会变得难以阅读。一种替代方法是使用异步代码,如在 doc:Concurrency 中所述。

捕获值

闭包可以从其定义时所在的外部上下文中捕获常量和变量。闭包可以在其体内引用并修改这些常量和变量的值,即使定义这些常量和变量的原始作用域已经不存在。

在 Swift 中,最简单的可以捕获值的闭包形式是嵌套函数,它写在另一个函数的主体内部。嵌套函数可以捕获其外部函数的任何参数,也可以捕获外部函数内部定义的任何常量和变量。

下面是一个名为`makeIncrementer`的函数示例,该函数包含一个名为`incrementer`的嵌套函数。嵌套的`incrementer()`函数捕获了两个值, `runningTotal`和`amount`, 从其周围的上下文中获取。 捕获这些值后, `incrementer` 是 `makeIncrementer` 返回的一个闭包,每次调用时会将 `runningTotal` 增加 `amount`。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

`makeIncrementer` 的返回类型是 `() -> Int`。这意味着它返回一个函数,而不是一个简单的值。它返回的函数没有参数,并且每次被调用时都会返回一个 `Int` 值。要了解函数可以返回其他函数的内容,请参阅 doc:Functions#Function-Types-as-Return-Types

makeIncrementer(forIncrement:) 函数定义了一个整数变量 `runningTotal`,用于存储递增器当前的累计值,该值将在返回时提供。该变量的初始值为 `0`。

makeIncrementer(forIncrement:) 函数有一个单一的 Int 参数,带有参数标签 forIncrement,以及参数名称 amount。传递给此参数的参数值指定了每次调用返回的增量函数时,runningTotal 应该增加多少。makeIncrementer 函数定义了一个名为 incrementer 的嵌套函数,该函数执行实际的增量操作。此函数简单地将 amount 加到 runningTotal 上,并返回结果。

当单独考虑时,嵌套的 incrementer() 函数可能会显得有些不寻常:

func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}

`incrementer()` 函数没有参数,但却在其函数体中引用了 `runningTotal` 和 `amount`。这是通过捕获对 `runningTotal` 和 `amount` 的引用实现的。 从外部函数获取并在自身函数体内使用它们。 引用捕获确保了在调用`makeIncrementer`结束时,`runningTotal`和`amount`不会消失,并且也确保了`runningTotal`在下次调用`incrementer`函数时是可用的。

注意:为了优化性能,Swift 可能会捕获并存储闭包未修改的值的副本,前提是该值在闭包创建后未被修改。

Swift 还会处理所有在变量不再需要时释放内存的工作。

以下是在实际操作中`makeIncrementer`的一个示例:

let incrementByTen = makeIncrementer(forIncrement: 10)

这个示例设置了一个名为 incrementByTen 的常量 使其引用一个每次调用时将 10 加到 runningTotal 变量中的增量函数。多次调用该函数可以展示这种行为:

incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30

如果你创建第二个增量函数,它将有自己的存储引用到一个新的独立的 runningTotal 变量:

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7

再次调用原始的增量函数(incrementByTen)继续增加它自己的 runningTotal 变量,并不会影响被 incrementBySeven 捕获的变量:

incrementByTen()
// returns a value of 40

注意:如果你将一个闭包赋值给类实例的一个属性,并且该闭包通过引用实例或其成员来捕获该实例,那么你将创建一个闭包与实例之间的强引用循环。Swift 使用 捕获列表 来打破这些强引用循环。有关更多信息,请参见 doc:AutomaticReferenceCounting#Strong-Reference-Cycles-for-Closures

闭包是引用类型

在上面的示例中, incrementBySevenincrementByTen 是常量,但这些常量所引用的闭包仍然能够增加它们捕获的 runningTotal 变量。这是因为函数和闭包是 引用类型

每当将函数或闭包赋值给常量或变量时,实际上就是将该常量或变量设置为指向该函数或闭包的引用。在上面的例子中,是闭包的选择固定不变,即`incrementByTen`这个闭包的引用是固定的,而不是闭包本身的內容。

这也意味着如果你将一个闭包赋值给两个不同的常量或变量,这两个常量或变量都会引用同一个闭包。

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50

incrementByTen()
// returns a value of 60

上面的示例显示了调用 `alsoIncrementByTen` 的效果 和调用 incrementByTen 是等价的。因为它们引用的是同一个闭包,所以都会增加并返回相同的累计值。

闭包越界

当闭包作为参数传递给函数,但在函数返回后被调用时,就说闭包越界了。当你声明一个接受闭包作为其参数之一的函数时,可以在参数类型前写上 `@escaping`,以表明该闭包允许越界。

闭包越界的一种方式是将其存储在一个函数外部定义的变量中。例如,许多启动异步操作的函数会接受一个闭包作为完成处理程序。函数在启动操作后返回,但闭包直到操作完成才会被调用——闭包需要越界,以便稍后调用。例如:

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

` someFunctionWithEscapingClosure(_:) ` 函数接受一个闭包作为参数,并将其添加到一个在函数外部声明的数组中。如果你没有在该函数的参数前加上 `@escaping`,将会得到一个编译时错误。

引用了 self 的闭包 如果 `self` 指的是某个类的实例,则需要特别考虑。在捕获 `self` 的闭包中,容易无意中创建强引用循环。关于引用循环的更多信息,请参见 doc:AutomaticReferenceCounting

通常,闭包会通过在闭包体内使用变量来隐式捕获这些变量,但在这种情况下,你需要显式指定。如果你想捕获`self`,在使用时显式写出`self`,或者将`self`包含在闭包的捕获列表中。显式写出`self`可以表达你的意图,并提醒你确认是否存在引用循环。例如,在下面的代码中,传递给`@4`的闭包: 明确地引用了 self。相比之下, someFunctionWithNonescapingClosure(_:) 传递的是一个闭包。 是一个非捕获闭包,这意味着它可以隐式地引用 self

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"

这是一个捕获 doSomething()self 的版本,通过将其包含在闭包的捕获列表中, 然后隐式地引用 self

class SomeOtherClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { [self] in x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

如果 `self` 是一个结构体或枚举类型的实例,你可以总是隐式地引用 `self`。然而,脱逃闭包不能捕获 `self` 的可变引用。 当 `self` 是一个结构体或枚举类型的实例时,结构体和枚举不允许共享可变性,如 doc:ClassesAndStructures#Structures-and-Enumerations-Are-Value-Types 所讨论的。

struct SomeStruct {
    var x = 10
    mutating func doSomething() {
        someFunctionWithNonescapingClosure { x = 200 }  // Ok
        someFunctionWithEscapingClosure { x = 100 }     // Error
    }
}

上面示例中对 ` someFunctionWithEscapingClosure ` 函数的调用是一个错误,因为它在一个可变方法内部,所以 `self` 是可变的。这违反了脱逃闭包不能捕获结构体中 `self` 的可变引用的规则。

自动闭包

一个 autoclosure 是一种自动创建的闭包,用于包裹作为函数参数传递的表达式。它不接受任何参数,当被调用时,它返回包裹在其内部的表达式的值。这种语法上的便利让你可以在写一个普通的表达式而不是显式的闭包时省略函数参数的花括号。

通常会调用带有 autoclosure 的函数,但很少实现这种类型的函数。例如, assert(condition:message:file:line:) 函数接受一个作为其 conditionmessage 参数的 autoclosure;其 condition 参数仅在调试构建中评估,而其 message 参数仅在 conditionfalse 时才评估。

autoclosure 让你可以延迟评估,因为闭包内部的代码直到调用闭包时才会运行。延迟评估对于具有副作用或计算成本高的代码非常有用,因为它让你可以控制何时评估这些代码。下面的代码展示了闭包如何延迟评估。

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` 来接受一个自动闭包。现在你可以像传递一个 `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!"

注意:过度使用自动闭包会使代码难以理解。上下文和函数名称应该清楚地表明延迟求值。

如果你想使用可以逃逸的自动闭包,可以同时使用 `@autoclosure` 和 `@escaping` 属性。`@escaping` 属性在前面的 doc:Closures#Escaping-Closures 中有描述。

// 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!"

在上述代码中,`collectCustomerProviders(_:)` 函数没有调用传给它的 `customerProvider` 参数闭包,而是将闭包追加到 `customerProviders` 数组中。数组在函数作用域之外声明,这意味着数组中的闭包可以在函数返回后执行。因此,`customerProvider` 参数的值必须允许逃逸出函数的作用域。

Beta 软件:

本文档包含有关正在开发中的 API 或技术的初步信息。这些信息可能会发生变化,根据本文档实现的软件应在最终操作系统的软件上进行测试。

了解更多关于使用 Apple 的 beta 软件 的信息。