閉包是自包含的函數代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數比較相似。
閉包可以捕獲和存儲其所在上下文中任意常量和變量的引用。被稱為包裹常量和變量。 Swift 會為你管理在捕獲過程中涉及到的所有內存操作。
閉包表達式
sorted 方法
sorted(by:) 方法接受一個閉包,該閉包函數需要傳入與數組元素類型相同的兩個值,并返回一個布爾類型值來表明當排序結束后傳入的第一個參數排在第二個參數前面還是后面。如果第一個參數值出現在第二個參數值前面,排序閉包函數需要返回true,反之返回false。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
閉包表達式語法
//閉包表達式語法有如下的一般形式
{ (parameters) -> returnType in
statements
}
閉包表達式參數 可以是 in-out 參數,但不能設定默認值。也可以使用具名的可變參數
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
閉包的函數體部分由關鍵字in引入。該關鍵字表示閉包的參數和返回值類型定義已經完成,閉包函數體即將開始。
根據上下文推斷類型
因為排序閉包函數是作為 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 自動為內聯閉包提供了參數名稱縮寫功能,你可以直接通過 1,$2 來順序調用閉包的參數,以此類推。
如果你在閉包表達式中使用參數名稱縮寫,你可以在閉包定義中省略參數列表,并且對應參數名稱縮寫的類型會通過函數類型進行推斷。in關鍵字也同樣可以被省略,因為此時閉包表達式完全由閉包函數體構成:
reversedNames = names.sorted(by: { $0 > $1 } )
運算符方法
實際上還有一種更簡短的方式來編寫上面例子中的閉包表達式。Swift 的 String 類型定義了關于大于號(>)的字符串實現,其作為一個函數接受兩個 String 類型的參數并返回 Bool 類型的值。而這正好與 sorted(by:) 方法的參數需要的函數類型相符合。因此,你可以簡單地傳遞一個大于號,Swift 可以自動推斷出你想使用大于號的字符串函數實現:
reversedNames = names.sorted(by: >)
尾隨閉包
如果你需要將一個很長的閉包表達式作為最后一個參數傳遞給函數,可以使用尾隨閉包來增強函數的可讀性。尾隨閉包是一個書寫在函數括號之后的閉包表達式,函數支持將其作為最后一個參數調用。在使用尾隨閉包時,你不用寫出它的參數標簽:
值捕獲
閉包可以在其被定義的上下文中捕獲常量或變量。即使定義這些常量和變量的原作用域已經不存在,閉包仍然可以在閉包函數體內引用和修改這些值。
Swift 中,可以捕獲值的閉包的最簡單形式是嵌套函數,也就是定義在其他函數的函數體內的函數。嵌套函數可以捕獲其外部函數所有的參數以及定義的常量和變量。
如果你將閉包賦值給一個類實例的屬性,并且該閉包通過訪問該實例或其成員而捕獲了該實例,你將在閉包和該實例間創建一個循環強引用。Swift 使用捕獲列表來打破這種循環強引用
閉包是引用類型
逃逸閉包
當一個閉包作為參數傳到一個函數中,但是這個閉包在函數返回之后才被執行,我們稱該閉包從函數中逃逸。當你定義接受閉包作為參數的函數時,你可以在參數名之前標注 @escaping,用來指明這個閉包是允許“逃逸”出這個函數的。
一種能使閉包“逃逸”出函數的方法是,將這個閉包保存在一個函數外部定義的變量中。舉個例子,很多啟動異步操作的函數接受一個閉包參數作為 completion handler。這類函數會在異步操作開始之后立刻返回,但是閉包直到異步操作結束后才會被調用。在這種情況下,閉包需要“逃逸”出函數,因為閉包需要在函數返回之后被調用
將一個閉包標記為 @escaping 意味著你必須在閉包中顯式地引用 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)
// 打印出 "200"
completionHandlers.first?()
print(instance.x)
// 打印出 "100"
自動閉包
自動閉包是一種自動創建的閉包,用于包裝傳遞給函數作為參數的表達式。這種閉包不接受任何參數,當它被調用的時候,會返回被包裝在其中的表達式的值。這種便利語法讓你能夠省略閉包的花括號,用一個普通的表達式來代替顯式的閉包。