Swift 中的閉包是獨立的程式區塊,可以接收參數來觸發運算並回傳值 閉包也會從自身的環境中捕獲值,並在運算中使用它們。 由於閉包是獨立的,它們可以像其他值一樣在程式中傳遞,也可以指定給常數和變數。
閉包聽起來可能很像 Swift 的函式;確實如此,而且這是有原因的。 Swift 中的函式是閉包的特例,它們必須有名稱,而且定義時使用的語法略有不同。
雖然 Swift 中的函式在技術上算是閉包,但當人們在 Swift 裡提到「closures」時,指的其實是閉包運算式。
閉包運算式的寫法是一串參數列表,接著是回傳型別和關鍵字in,然後是閉包的主體,全部包在一對大括號裡:
{ (a: Double, b: Double) -> Double in
return (a + b) / 2.0
}
這定義了一個型別為(Double, Double) -> Double的閉包運算式,執行方式類似於同型別的函式,只要將它套用到適當型別的參數上:
{ (a: Double, b: Double) -> Double in
return (a + b) / 2.0
}(10.0, 15.0)
// returns 12.5
由於每次想執行閉包時都寫出完整的閉包運算式並不太方便,閉包可以像 Swift 中其他任何值一樣被指定給名稱。 之後只要把這個名稱套用到參數上,就能呼叫它:
let mean = { (a: Double, b: Double) -> Double in
(a + b) / 2.0
}
mean(13.0, 100.0)
// returns 56.5
如上面所見,閉包和一般函式一樣,如果主體只有單一運算式,可以省略return關鍵字。
閉包最常和高階函式一起使用,不是作為參數傳入,就是作為結果回傳:
[11, 75, 3, 99, 53].contains(where: { (x: Int) -> Bool in x > 100 })
// returns false
["apple", "ball", "carrot"].sorted(by: { (s1: String, s2: String) -> Bool in s1.count < s2.count })
// returns ["ball", "apple", "carrot"]
func makeAdder(base: Int) -> (Int) -> Int {
{ (x: Int) -> Int in base + x }
}
你好,Double-Null0111 探員,我們又見面了。
UMBRA 的勢力又開始蠢蠢欲動,這次要靠你來粉碎他們最新的陰謀。 我們的情報部門告訴我們,那些 Minions 正打算在州長的舞會上引爆一枚臭彈。
由於 Minions 實在太笨手笨腳,他們常常在實驗室和辦公室裡不小心啟動臭彈。 又因為他們老是忘記怎麼拆除自己的臭彈,所以他們做了一套系統,會根據炸彈的 ID 號碼產生拆彈指令,列出電線必須剪斷的順序。
你的任務是寫出程式碼,讓你能在舞會上的臭彈引爆前把它拆除。
每枚臭彈上都有三條顏色不同的電線。
撰寫一個閉包,接受一個 (String, String, String) 元組,並回傳把前兩個元素順序對調後的元組。
將這個閉包指定給名稱flip。
請注意,型別必須是 ChangeClosure(或可傳送的型別),型別定義已經寫在 stub 檔案裡。
否則程式碼將無法編譯。
flip(("red", "yellow", "blue"))
// Returns ("yellow", "red", "blue")
撰寫一個閉包,接受一個 (String, String, String) 元組,並回傳把電線順序向左旋轉後的元組。
將這個閉包指定給名稱rotate。
請注意,型別必須是 ChangeClosure(或可傳送的型別),型別定義已經寫在 stub 檔案裡。
rotate(("red", "yellow", "blue"))
// Returns ("yellow", "blue", "red")
為了找出剪斷臭彈電線的順序,你需要實作一個函式,根據臭彈的 ID 號碼產生正確的洗牌函式。
實作這個函式:
makeShuffle(
flipper: @escaping ((String, String, String)) -> (String, String, String),
rotator: @escaping ((String, String, String)) -> (String, String, String)
) -> ([UInt8], (String, String, String)) -> (String, String, String)
它接受一個翻轉兩條電線的閉包,以及一個旋轉三條電線的閉包,並回傳一個閉包。 這個回傳的閉包會接受臭彈的 ID 號碼和三條電線的順序,然後計算出電線需要剪斷的順序。 ID 號碼由八個位元組成,每個位元會決定要翻轉還是旋轉電線。
從最右邊的位元開始,針對 ID 號碼中的每個位元,如果該位元是 0,就把 flipper 閉包套用到電線元組上;如果是 1,就套用 rotator 閉包,得到電線的新狀態。
當 ID 的八個位元都套用完對應的閉包後,電線的最終狀態就是它們需要被剪斷的順序。
let shuffler = makeShuffle(flipper: flip, rotator: rotate)
// Returns ([UInt8], (String, String, String)) -> (String, String, String)
shuffler([1, 0, 0, 1, 1, 0, 1, 1], ("red", "yellow", "blue"))
// Returns ("red", "blue", "yellow")