閉包是自包含的函數代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數比較相似。
閉包可以捕獲和存儲其所在上下文中任意常量和變量的引用。被稱為包裹常量和變量。 Swift 會為你管理在捕獲過程中涉及到的所有內存操作。
在函數章節中介紹的全局和嵌套函數實際上也是特殊的閉包,閉包采取如下三種形式之一:
全局函數是一個有名字但不會捕獲任何值的閉包
嵌套函數是一個有名字并可以捕獲其封閉函數域內值的閉包
閉包表達式是一個利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包
Swift 的閉包表達式擁有簡潔的風格,并鼓勵在常見場景中進行語法優化,主要優化如下:利用上下文推斷參數和返回值類型
隱式返回單表達式閉包,即單表達式閉包可以省略 return 關鍵字
參數名稱縮寫
尾隨閉包語法
閉包表達式
閉包表達式是一種利用簡潔語法構建內聯閉包的方式。閉包表達式提供了一些語法優化,使得撰寫閉包變得簡單明了。下面閉包表達式的例子通過使用幾次迭代展示了 sorted(by:) 方法定義和語法優化的方式。每一次迭代都用更簡潔的方式描述了相同的功能。
sorted 方法
Swift 標準庫提供了名為 sorted(by:) 的方法,它會根據你所提供的用于排序的閉包函數將已知類型數組中的值進行排序。一旦排序完成,sorted(by:) 方法會返回一個與原數組大小相同,包含同類型元素且元素已正確排序的新數組。原數組不會被 sorted(by:) 方法修改。
下面的閉包表達式示例使用 sorted(by:) 方法對一個 String 類型的數組進行字母逆序排序。以下是初始數組:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sorted(by:) 方法接受一個閉包,該閉包函數需要傳入與數組元素類型相同的兩個值,并返回一個布爾類型值來表明當排序結束后傳入的第一個參數排在第二個參數前面還是后面。如果第一個參數值出現在第二個參數值前面,排序閉包函數需要返回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 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
如果第一個字符串(s1)大于第二個字符串(s2),backward(::) 函數會返回 true,表示在新的數組中 s1 應該出現在 s2 前。對于字符串中的字符來說,“大于”表示“按照字母順序較晚出現”。這意味著字母 "B" 大于字母 "A" ,字符串 "Tom" 大于字符串 "Tim"。該閉包將進行字母逆序排序,"Barry" 將會排在 "Alex" 之前。
然而,以這種方式來編寫一個實際上很簡單的表達式(a > b),確實太過繁瑣了。對于這個例子來說,利用閉包表達式語法可以更好地構造一個內聯排序閉包。
閉包表達式語法
閉包表達式語法有如下的一般形式:
{ (parameters) -> returnType in
statements
}
閉包表達式參數 可以是 in-out 參數,但不能設定默認值。也可以使用具名的可變參數(譯者注:但是如果可變參數不放在參數列表的最后一位的話,調用閉包的時時編譯器將報錯。元組也可以作為參數和返回值。
下面的例子展示了之前 backward(::) 函數對應的閉包表達式版本的代碼:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2})
需要注意的是內聯閉包參數和返回值類型聲明與 backward(::) 函數類型聲明相同。在這兩種方式中,都寫成了 (s1: String, s2: String) -> Bool。然而在內聯閉包表達式中,函數和返回值類型都寫在大括號內,而不是大括號外。
- 根據上下文推斷類型
因為排序閉包函數是作為 sorted(by:) 方法的參數傳入的,Swift 可以推斷其參數和返回值的類型。sorted(by:) 方法被一個字符串數組調用,因此其參數必須是 (String, String) -> Bool 類型的函數。這意味著 (String, String) 和 Bool 類型并不需要作為閉包表達式定義的一部分。因為所有的類型都可以被正確推斷,返回箭頭(->)和圍繞在參數周圍的括號也可以被省略:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
- 單表達式閉包隱式返回
單行表達式閉包可以通過省略 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 } )
在這個例子中,0和1表示閉包中第一個和第二個 String 類型的參數。
- 運算符方法
實際上還有一種更簡短的方式來編寫上面例子中的閉包表達式。Swift 的 String 類型定義了關于大于號(>)的字符串實現,其作為一個函數接受兩個 String 類型的參數并返回 Bool 類型的值。而這正好與 sorted(by:) 方法的參數需要的函數類型相符合。因此,你可以簡單地傳遞一個大于號,Swift 可以自動推斷出你想使用大于號的字符串函數實現:
reversedNames = names.sorted(by: >)