★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公眾號:山青詠芝(shanqingyongzhi)
➤博客園地址:山青詠芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9728063.html
➤如果鏈接不是山青詠芝的博客園地址,則可能是爬取作者的文章。
➤原文已修改更新!強烈建議點擊原文地址閱讀!支持作者!支持原創!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
閉包是獨立的功能塊,可以在代碼中傳遞和使用。Swift中的閉包類似於C和Objective-C中的塊以及其他編程語言中的lambda。
閉包可以從定義它們的上下文中捕獲和存儲對任何常量和變量的引用。這稱為關閉這些常量和變量。Swift為您處理捕獲的所有內存管理。
注意
如果您不熟悉捕獲的概念,請不要擔心。下面在捕獲值中對此進行了詳細說明。
正如Function中介紹的那樣,全局和嵌套函數實際上是閉包的特殊情況。閉包采用以下三種形式之一:
- 全局函數是具有名稱且不捕獲任何值的閉包。
- 嵌套函數是具有名稱的閉包,可以從其閉包函數捕獲值。
- 閉包表達式是用輕量級語法編寫的未命名的閉包,可以從其周圍的上下文中捕獲值。
Swift的閉包表達式具有簡潔明了的風格,其優化功能鼓勵在常見情況下使用簡潔,簡潔的語法。這些優化包括:
- 從上下文推斷參數和返回值類型
- 單表達式閉包的隱式返回
- 速記參數名稱
- 尾隨閉包語法
閉包表達式
嵌套函數中介紹的嵌套函數是命名和定義自包含代碼塊作為較大函數的一部分的便捷方法。但是,有時在沒有完整的聲明和名稱的情況下編寫類似函數的結構的較短版本很有用。當您使用以函數作為其一個或多個參數的函數或方法時,尤其如此。
閉包表達式是一種以簡短,集中的語法編寫內聯閉包的方法。閉包表達式提供了幾種語法優化,以簡化形式編寫閉包,而不會造成任何不清楚或意圖。下面的閉包表達式示例通過sorted(by:)
在多個迭代中完善方法的單個示例來說明這些優化,每個迭代以更簡潔的方式表示相同的功能。
排序方法
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"]
如果第一個字符串(s1
)大於第二個字符串(s2
),則backward(_:_:)
函數將返回true
,指示該字符串s1
應出現s2
在已排序數組的前面。對於字符串中的字符,“大於”表示“在字母表中出現的時間晚於”。這意味着字母"B"
“大於”字母"A"
,並且字符串"Tom"
大於string "Tim"
。這給出了反向的字母排序,並"Barry"
放在之前"Alex"
,依此類推。
但是,這實際上是編寫單表達式函數()的漫長過程。在此示例中,最好使用閉包表達式語法內聯地編寫排序閉包。a > b
閉包表達式語法
閉包表達式語法具有以下一般形式:
- { (parameters) -> return type in
- statements
- }
該參數在封閉表達式語法可以在輸出參數,但是他們不能有一個默認值。如果您命名可變參數,則可以使用可變參數。元組也可用作參數類型和返回類型。
下面的示例顯示了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:)
方法是在字符串數組上調用的,因此其參數必須是type的函數。這意味着和類型不必作為閉包表達式定義的一部分來編寫。由於可以推斷所有類型,因此還可以省略返回箭頭()和參數名稱周圍的括號:(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
速記參數名稱
雨燕自動提供速記參數名內聯閉包,它可以使用的名稱,指的是關閉的參數值$0
,$1
,$2
,等等。
如果在閉包表達式中使用這些速記參數名稱,則可以從其定義中省略閉鎖的參數列表,而速記參數名稱的數量和類型將從所需的函數類型中推斷出來。的in
關鍵字也可以被省略,因為封閉件表達是由完全其身體的:
- reversedNames = names.sorted(by: { $0 > $1 } )
在這里,$0
並$1
參考閉包的第一個和第二個String
參數。
操作員方法
實際上,上面有一種更短的編寫閉包表達式的方法。Swift的String
類型將大於操作符(>
)的特定於字符串的實現定義為具有兩個type參數的方法String
,並返回type的值Bool
。這與方法所需的方法類型完全匹配sorted(by:)
。因此,您只需傳遞大於號運算符,Swift就會推斷出您想使用其特定於字符串的實現:
- reversedNames = names.sorted(by: >)
有關運算符方法的更多信息,請參見運算符方法。
尾隨閉包
如果您需要將閉包表達式作為函數的最終參數傳遞給函數,並且閉包表達式很長,那么將其寫為尾隨閉包可能會很有用。您可以在函數調用的括號后面編寫尾隨閉包,即使尾隨閉包仍然是函數的參數。使用尾隨閉包語法時,不要在函數調用的過程中為第一個閉包編寫參數標簽。一個函數調用可以包含多個尾隨的閉包。但是,下面的前幾個示例使用單個尾隨閉包。
- 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
- }
上面“ 閉包表達式語法”部分中的字符串排序閉包可以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
數組來創建String
值數組,方法是將閉包表達式map(_:)
作為尾隨閉包傳遞給數組的方法:
- 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
使用余數運算符()計算的最后一位,並使用該位在字典中查找適當的字符串。閉包可用於創建任何大於零的整數的字符串表示形式。number % 10
digitNames
注意
對digitNames
字典下標的調用后跟一個感嘆號(!
),因為字典下標返回一個可選值,以指示如果鍵不存在,則字典查找會失敗。在上面的示例中,保證將始終是字典的有效下標鍵,因此使用感嘆號強制拆開存儲在下標的可選返回值中的值。number % 10
digitNames
String
從檢索到的字符串digitNames
辭典被添加到前面的output
,在反向有效建立數字的字符串版本。(該表達式給出了for ,for 和for的值。)number % 10
6
16
8
58
0
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:)
函數將其網絡任務分派到后台,並在網絡任務完成時調用兩個完成處理程序之一。以這種方式編寫函數可以讓您將負責處理網絡故障的代碼與成功下載后更新用戶界面的代碼完全區分開,而不是僅使用一個處理這兩種情況的閉包。
捕捉價值
閉包可以從定義它的周圍上下文中捕獲常量和變量。然后,即使定義了常量和變量的原始范圍不再存在,閉包也可以從其主體內部引用和修改這些常量和變量的值。
在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
為。這意味着它將返回一個function,而不是一個簡單的值。它返回的函數沒有參數,並且每次調用都返回一個值。要了解函數如何返回其他函數,請參見函數類型作為返回類型。() -> Int
Int
該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
從周圍的功能和其自身的函數體中使用它們。通過引用捕獲可以確保調用結束時runningTotal
且amount
不會消失makeIncrementer
,並且還可以確保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使用捕獲列表來打破這些強大的參考周期。有關更多信息,請參見強閉包參考循環。
閉包是引用類型
在上面的示例中,incrementBySeven
和incrementByTen
是常量,但是這些常量引用的閉包仍然能夠增加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
轉義的閉包可以輕松地意外創建一個強大的參考周期。有關參考循環的信息,請參見自動參考計數。
通常,閉包通過在閉包主體中使用變量來隱式捕獲變量,但是在這種情況下,您需要明確表示。如果要捕獲self
,請self
在使用時明確編寫,或包括self
在閉包的捕獲列表中。self
明確寫作可以表達您的意圖,並提醒您確認沒有參考周期。例如,在下面的代碼中,傳遞給的閉包顯式地someFunctionWithEscapingClosure(_:)
引用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
是結構實例或枚舉的可變引用。結構和枚舉不允許共享的可變性,如“ 結構和枚舉是值類型”中所述。
- struct SomeStruct {
- var x = 10
- mutating func doSomething() {
- someFunctionWithNonescapingClosure { x = 200 } // Ok
- someFunctionWithEscapingClosure { x = 100 } // Error
- }
- }
someFunctionWithEscapingClosure
上面示例中對函數的調用是錯誤的,因為它在mutation方法內部,因此self
是可變的。這違反了轉義閉包不能捕獲self
對結構的可變引用的規則。
自動關閉
一個autoclosure是自動創建來包裝被真實作為參數傳遞給函數的表達式的封閉件。它不接受任何參數,並且在調用它時,它返回包裝在其中的表達式的值。這種語法上的便利性使您可以通過編寫正則表達式而不是顯式閉包來省略函數參數的花括號。
調用具有自動關閉功能的函數很常見,但是實現這種功能並不常見。例如,該assert(condition:message:file:line:)
函數對其condition
和message
參數進行自動關閉。它condition
僅在調試參數進行評估,並建立其message
僅在參數評估condition
是false
。
自動閉包可以延遲評估,因為在調用閉包之前,內部代碼不會運行。延遲評估對於具有副作用或計算量大的代碼很有用,因為它使您可以控制何時評估該代碼。下面的代碼顯示了閉包如何延遲評估。
- 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
屬性在逃逸閉包中進行了描述。
- // 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
必須允許參數的值轉義函數的范圍。