Swift: 8 閉包
閉包是一個自包含的功能塊,它可以在代碼中傳遞和使用。Swift中的閉包跟Objective-C中的block的概念類似。閉包最經典的行為就是可以捕獲和存儲對其所在定義位置的上下文的任何常量和變量的引用。
閉包表達式
- 閉包表達式的語法大致形式如下:在大括號"{}"里面包含參數類型、返回值類型以及程序體。
{ ( parameters ) -> return type in
statements
}
閉包表達式語法可以使用常量、變量和inout類型作為形參。不能提供默認值。 也可以在形參列表的最后使用可變參數。元組也可以作為形參類型和返回類型。
- 當閉包作為一個函數的參數傳遞過去的時候,這也是閉包最常使用的場景。比如對于數組排序,數組類型有個sorted(by:)函數,其接受一個(String, String)->Bool形式的閉包作為參數,這種情況下,Swift能夠推斷出閉包的參數和返回值類型,那么這些類型加上箭頭(->)都可以省略不寫。
var names = ["Baby","Kitty","Amy","Clida","Emick"]
//閉包完整寫法
names.sorted(by:{(s1: String, s2: String)->Bool in return s1 > s2 })
//根據上下文類型推斷
let newNames = names.sorted(by:{s1,s2 in return s1 < s2})
print(newNames)
//打?。篬"Amy", "Baby", "Clida", "Emick", "Kitty"]
代碼執行結果如下圖。
- 如果閉包體里面只有一行表達式的話,return關鍵字也可以省略。
names.sorted(by:{s1,s2 in s1 < s2})
- Swift自動為內聯閉包提供了實參名縮寫功能,你可以直接通過$0、$1、$2...來引用實參。如果你在閉包表達式中使用實參名縮寫,你可以在定義中省略實參列表,并且縮寫的實參名的個數和類型會通過函數類型進行推斷。in關鍵詞同樣也可以被省略,因為此時閉包表達式完全由閉包主體構成。
names.sorted(by:{ $0>$1 })
后置閉包(trailing closure)
如果閉包是某個函數的最后一個參數,而閉包表達式在函數的參數列表中寫的時候又比較長,這個時候就可以考慮使用后置閉包。后置閉包就是把閉包表達式寫在函數的右括號的后面,雖然這個時候閉包依然是函數的參數,不過可以不用再寫參數的外部名稱了。
//假設有個這樣的函數
func withAClosure(closure:()->void){
}
//一般函數調用,這樣寫
withAClosure(closure: {
//閉包體
}
)
//可以用后置閉包代替
withAClosure(){
//比包體
}
如果函數只有閉包類型的這一個參數,那么函數調用的時候,左右括號"()"也可以直接省略不寫。
//后置閉包寫法
let newNames = names.sorted(){$0 > $1)
//括號可以省略了
let newNames1 = names.sorted{$0 > $1}
后置閉包對于那些不方便在一行代碼里面寫完閉包邏輯的函數調用非常有用,接下來演示使用數組類型的map(_:)函數利用閉包把數組中的每一個值都通過閉包進行轉換,最終會合成一個新的數組。
let nums = [41,56,92,13,84,]
let allNums = ["壹","貳","叁","肆","伍","陸","柒","捌","玖","拾"]
let strs = nums.map{ (num)->String in
var number = num
var output = ""
repeat {
output = allNums[number % 10] + output
number /= 10
} while number > 0
return output
}
print(strs)
//打印結果:["伍貳", "陸柒", "拾叁", "貳肆", "玖伍"]
代碼運行結果如下圖。
捕獲值
閉包可以在其定義的上下文中捕獲常量或者變量。即使定義這些常量和變量所在的原始域已經不存在了,閉包仍然可以在閉包函數體內引用和修改這些值。
Swift中最簡單的閉包形式是嵌套函數。嵌套函數可以捕獲外層函數所有的實參以及定義的常量和變量。
func createMulti(base amount: Int)->()-> Int {
var total = 1
func multi()-> Int{
total *= amount
return total
}
return multi
}
let multiF = createMulti(base:2)
print(multiF())
//打?。?
print(multiF())
//打?。?
print(multiF())
//打?。?
let multiT = createMulti(base:10)
print(multiT())
//打?。?0
print(multiF())
//打印:16
代碼運行結果如下圖所示。
閉包是引用類型
上面的例子中,multiF和multiT都是常量,但是這些常量指向的閉包仍然可以改變其捕獲的total變量,這是因為閉包和函數一樣都是引用類型。同時可以看出,對于不同的閉包,他們各自保存一份從外部函數捕獲到的變量或者常量的引用,互不干擾。