Swift 閉包 (Closures)

閉包 (Closures) 是自包含的程式碼塊,可以在程式碼中被傳遞和使用。Swift 的閉包類似於 C 和 Objective-C 的 Block,以及其他語言中的 Lambda。

閉包可以捕獲 (Capture) 和儲存其所在上下文中的常數和變數的引用,這就是所謂的「閉合包裹變數」,因此得名「閉包」。

閉包表達式語法

一般的函式其實就是一種特殊的閉包。而我們常說的「閉包表達式」通常指這種輕量級的語法:

{ (parameters) -> returnType in
    statements
}

例子:Swift 標準庫的 sorted(by:) 方法接受一個閉包來決定排序規則。

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

// 完整的閉包語法
var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

語法優化

Swift 的閉包語法在設計上非常簡潔,可以進行多次簡化:

1. 推斷型別

因為 sorted(by:) 預期接收 (String, String) -> Bool 的閉包,所以我們可以省略型別:

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

2. 單表達式隱式回傳

單行閉包可以省略 return

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

3. 參數名稱縮寫

Swift 自動為閉包提供參數縮寫 $0, $1, $2 等:

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

4. 尾隨閉包 (Trailing Closures)

這是 Swift 最常見的寫法。如果閉包是函式的最後一個參數,你可以將閉包寫在括號外面:

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

如果函式只有這一個閉包參數,甚至連括號都可以省略:

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

逃逸閉包 (Escaping Closures)

當一個閉包作為參數傳入函式,但在函式返回之後才被執行 (例如非同步操作的回呼 Callback),我們必須將該閉包標記為 @escaping

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    // 閉包被儲存在陣列中,將在函式結束後才被呼叫
    completionHandlers.append(completionHandler)
}

自動閉包 (Autoclosures)

@autoclosure 是一種語法糖,它讓你傳遞一個表達式作為參數,Swift 會自動把它包裝成一個閉包。這常用於延遲求值 (Lazy Evaluation)。

// 參數是 () -> Bool,但使用 @autoclosure
func logIfTrue(_ predicate: @autoclosure () -> Bool) {
    if predicate() {
        print("True!")
    }
}

// 呼叫時看起來像傳入普通布林值,但其實 2 > 1 直到函式內部調用 predicate() 時才執行
logIfTrue(2 > 1)

閉包是 Swift 功能強大且優雅的關鍵,在處理集合操作 (map, filter, reduce) 和非同步程式設計時無處不在。