- 閉包是功能性自包含模塊,可以在代碼中被傳遞和使用。 Swift 中的閉包與 C 和Objective-C 中的 blocks 以及其他一些編程語言中的 lambdas 比較相似。
- 閉包可以捕獲和存儲其所在上下文中任意常量和變量的引用。 這就是所謂的閉合并包裹著這些常量和變量,俗稱閉包。Swift 會為您管理在捕獲過程中涉及到的內存操作。
- 在 函數 章節中介紹的全局和嵌套函數實際上也是特殊的閉包,閉包采取如下三種形式之一:
- 全局函數是一個有名字但不會捕獲任何值的閉包
- 嵌套函數是一個有名字并可以捕獲其封閉函數域內值的閉包
- 閉包表達式是一個利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的沒有名字的閉包
閉包表達式
閉包表達式是一種利用簡潔語法構建內聯閉包的方式。 閉包表達式??供了一些語法優化,使得撰寫閉包變得簡單明了。 下面閉包表達式的例子通過使用幾次迭代展示了 sort 函數定義和語法優化的方式。 每一次迭代都用更簡潔的方式??述了相同的功能。
sort 函數
下面的閉包表達式示例使用 sort 函數對一個 String 類型的數組進行字母逆序排序,以下是初始數組值:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
排序函數有兩個參數:
- 已知類型值的數組。
- 一個閉包,采用相同類型的數組的內容的兩個參數,并返回一個布爾值來表示是否將第一個值在排序時放到第二個值的前面或是后面。如果第一個值應該出現第二個值之前,閉包需要返回 true,否則返回 false。
func backwards(s1: String, s2: String) -> Bool {2. return s1 > s2.
}
var reversed = sort(names, backwards)5.
// reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
閉包表達式語法
閉包表達式語法有如下一般形式:
{ (parameters) -> returnType in
statements
}
下面的例子展示了之前 backwards 函數對應的閉包表達式版本的代碼:
reversed = sort(names, { (s1: String, s2: String) -> Bool in
return s1 > s2
})
根據上下文推斷類型
因為排序閉包是作為函數的參數進行傳入的,Swift 可以推斷其參數和返回值的類型。 sort期望第二個參數是類型為 (String, String) -> Bool 的函數,因此實際上 String, String 和Bool 類型并不需要作為閉包表達式定義中的一部分。 因為所有的類型都可以被正確推斷,返回箭頭 (->) 和 圍繞在參數周圍的括號也可以被省略:
reversed = sort(names, { s1, s2 in return s1 > s2 } )
單行表達式閉包可以省略 return
reversed = sort(names, { s1, s2 in s1 > s2 } )
參數名簡寫
如果您在閉包表達式中使用參數名稱簡寫,您可以在閉包參數列表中省略對其的定義,并且對應參數名稱簡寫的類型會通過函數類型進行推斷。 in 關鍵字也同樣可以被省略,因為此時閉包表達式
reversed = sort(names, { $0 > $1 } )
//$0 和 $1 表示閉包中第一個和第二個 String 類型的參數。
運算符函數
實際上還有一種更簡短的方式來撰寫上面例子中的閉包表達式。 Swift 的 String 類型定義了關于大于號 (>) 的字符串實現,讓其作為一個函數接受兩個 String 類型的參數并返回Bool 類型的值。而這正好與 sort 函數的第二個參數需要的函數類型相符合。因此,您可以簡單地傳遞一個大于號,Swift 可以自動推斷出您想使用大于號的字符串函數實現:
reversed = sort(names, >)
Trailing 閉包
如果您需要將一個很長的閉包表達式作為最后一個參數傳遞給函數,可以使用 trailing 閉包來增強函數的可讀性。
Trailing 閉包是一個書寫在函數括號之外(之后)的閉包表達式,函數支持將其作為最后一個參數調用。
func someFunctionThatTakesAClosure(closure: () -> ()) {
// 函數體部分
}
// 以下是不使用 trailing 閉包進行函數調用
someFunctionThatTakesAClosure({
// 閉包主體部分
})// 以下是使用 trailing 閉包進行函數調用
someFunctionThatTakesAClosure() {
// 閉包主體部分
}
捕獲 (Caputure)
閉包可以在其定義的上下文中捕獲常量或變量。 即使定義這些常量和變量的原作用域已經不存在,閉包仍然可以在閉包函數體內引用和修改這些值。
Swift 最簡單的閉包形式是嵌套函數,也就是定義在其他函數體內的函數。 嵌套函數可以捕獲其外部函數所有的參數以及定義的常量和變量。
下例為一個叫做 makeIncrementor 的函數,其包含了一個叫做 incrementor 嵌套函數。嵌套函數 incrementor 從上下文中捕獲了兩個值,runningTotal 和 amount。 之后makeIncrementor 將 incrementor 作為閉包返回。 每次調用 incrementor 時,其會以amount 作為增量增加 runningTotal 的值。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
incrementor 函數并沒有獲取任何參數,但是在函數體內訪問了 runningTotal 和 amount變量。這是因為其通過捕獲在包含它的函數體內已經存在的 runningTotal 和 amount 變量
而實現。
由于沒有修改 amount 變量,incrementor 實際上捕獲并存儲了該變量的一個副本,而該副本隨著 incrementor 一同被存儲。
然而,因為每次調用該函數的時候都會修改 runningTotal 的值,incrementor 捕獲了當前runningTotal 變量的引用,而不是僅僅復制該變量的初始值。捕獲一個引用保證了當makeIncrementor 結束時候并不會消失,也保證了當下一次執行 incrementor 函數時,runningTotal 可以繼續增加。
下面為一個使用 makeIncrementor 的例子:
let incrementByTen = makeIncrementor(forIncrement: 10)
該例子定義了一個叫做 incrementByTen 的常量,該常量指向一個每次調用會加 10 的incrementor 函數。 調用這個函數多次可以得到以下結果:
incrementByTen()
// 返回的值為10
incrementByTen()
// 返回的值為205. incrementByTen()
// 返回的值為30
如果您創建了另一個 incrementor,其會有一個屬于自己的獨立的 runningTotal 變量的引用。 下面的例子中,incrementBySevne 捕獲了一個新的 runningTotal 變量,該變量和incrementByTen 中捕獲的變量沒有任何聯系:
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()3. // 返回的值為74. incrementByTen()
// 返回的值為40