Swift獨立函數代碼塊(閉包)
閉包是功能性自包含模塊,可以在代碼中被傳遞和使用。Swift中的閉包與C/OC中的blocks以及其他一些編程語言中的lambdas相似。
閉包可以捕獲和存儲它所在上下文中任意常量和變量的引用。這就是所謂的閉合幷包裹着這些常量和變量,俗稱閉包。Swift會爲你管理在捕獲過程中涉及到的內存操作。
在函數裏的全局和嵌套函數實際上也是特殊的閉包,閉包採取如下三種形式之一:
1. 全局函數是一個有名字但不會捕獲任何值的閉包。
2. 嵌套函數是一個有名字並可以捕獲其封閉函數域內值的閉包。
3. 閉包表達式是一個利用輕量級語法縮寫的可以捕獲其上下文中變量或常量值的沒有名字的閉包。
Swift的閉包表達式擁有簡潔的風格,並鼓勵在常見場景中進行語法優化,主要優化有:
1. 利用上下文推斷參數和返回類型。
2. 單表達式閉包可以省略return關鍵字。
3. 參數名稱縮寫。
4. Trailing閉包語法。
閉包表達式
閉包表達式是一種利用簡潔語法構建內聯閉包的方式。閉包表達式提供了一些語法優化,使得撰寫閉包變得簡單明瞭。
sort函數
Swift標準庫提供了sort函數,會根據你提供的排序閉包將已知類型數組中的值進行排序。一旦排序完成,函數會返回一個與元素組大小相同的新數組,該數組中包含已經正確排序的同類型元素。
① 使用sort函數對一個String類型的數組進行字母逆序排序:
let names = ["Jolin","Elva","Fish","Stefanie"]
func backwards(s1: String,s2: String)->Bool{
return s1 > s2
}
var reversed = sort(names,backwards)
println(reversed)
//[Stefanie, Jolin, Fish, Elva]
該例子對一個String類型的數組進行排序,因此排序閉包需爲(String,String)->Bool類型的函數。
提供排序閉包的一種方法是撰寫一個符合其類型要求的普通函數,並將其作爲sort函數的第二個參數傳入。
利用閉合表達式語句構造一個內聯排序閉包。
閉包表達式語法:
閉包表達式語法有如下一般形式:
{(parameters)-> returnType in statements}
閉包表達式語法可以使用常量,變量和inout類型作爲參數,不提供默認值。
也可以在參數列表的最後使用可變參數。元組也可以作爲參數和返回值。
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,{(s1: String,s2: String) -> Bool in return s1 > s2})
println(reversed)
////[Stefanie, Jolin, Fish, Elva]
需要注意的是內聯閉包參數和返回值類型聲明與backwards函數類型聲明相同。在這兩種方式中,都寫成了(s1: String,s2: String)-> Bool。然而在內聯閉包表達式中,函數和返回值類型都寫在大括號內,而不是大括號外。
閉包的函數體部分由關鍵字in引入。該關鍵字表示閉包的參數和返回值類型定義已經完成,閉包函數體即將開始。
根據上下文推斷類型
因爲排序閉包是作爲函數的參數進行傳入的,Swift可以推斷其參數和返回值的類型。sort期望第二個參數是類型爲(String,String)->Bool的函數,因此實際上String,String和Bool類型並不需要作爲閉包表達式定義中的一部分。以爲所有的類型都可以被正確推斷,返回箭頭(->)和圍繞在參數周圍的括號也可以被省略:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,{s1,s2 in return s1 > s2})
println(reversed)
////[Stefanie, Jolin, Fish, Elva]
實際上任何情況下,通過內聯閉包表達式構造的閉包作爲參數傳遞給函數時,都可以推斷出閉包的參數和返回值類型。這意味着你幾乎不需要利用完整格式構造任何內聯閉包。
單行表達式閉包可以省略 return
單行表達式閉包可以通過隱藏return關鍵字來隱式返回單行表達式的結果:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,{s1,s2 in s1 < s2})
println(reversed)
//[Elva, Fish, Jolin, Stefanie]
在這個例子裏,sort函數的第二個參數函數類型明確了閉包必須返回一個Bool類型值。因爲閉包函數體只包含了一個單一表達式(s1 < s2),該表達式返回Bool類型值。無歧義,return關鍵字可以省略。
參數名稱縮寫
Swift自動爲內聯函數提供了參數名稱縮寫功能,你可以直接通過$0,$1,$2來順序調用閉包的參數。
如果你在閉包表達式中使用參數名稱縮寫,你可以在閉包參數列表中省略對其的定義,並且對應參數名稱縮寫的類型會通過函數類型進行推斷。in關鍵字也同樣可以被省略,因爲此時閉包表達式完全由閉包函數體構成:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,{$0 > $1})
println(reversed)
//[Stefanie, Jolin, Fish, Elva]
運算符函數
Swift的String類型定義了關於大於號(>)的字符串實現,其作爲一個函數接受兩個String類型的參數並返回Bool類型的值。而正好與sort函數的第二個參數需要的函數類型相符合。因此,你可以簡單傳遞一個大於號,Swift可以自動推斷你想使用大於號的字符串函數實現:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names,>)
println(reversed)
//[Stefanie, Jolin, Fish, Elva]
Trailing閉包
如果你需要將一個很長的閉包表達式作爲最後一個參數傳遞給函數,可以使用trailing閉包來增強函數的可讀性。Trailing閉包是一個書寫在函數括號之外(之後)的閉包表達式,函數支持將其最後一個參數調用。
func someFunctionThatTakesAClosure(closure:()->()){
//函數體部分
}
//以下是不使用trailing閉包進行函數調用
someFunctionThatTakesAClosure({
//閉包主題部分
})
//以下是使用trailing閉包進行函數調用
someFunctionThatTakesAClosure(){
//閉包主題部分
}
注意:如果函數只需要閉包表達式一個參數,當你使用trailing閉包時,你甚至可以把()省略掉。
上述中作爲sort函數參數的字符串排序閉包可以改寫爲:
let names = ["Jolin","Elva","Fish","Stefanie"]
var reversed = sort(names){
$0 > $1
}
println(reversed)
//[Stefanie, Jolin, Fish, Elva]
當閉包非常長以至於不能在一行中進行書寫時,Trailing閉包變得非常有用。
舉例:Swift的Array類型有一個map方法,其獲取一個閉包表達式作爲其唯一參數。數組中的每一個元素調用一次該閉包函數,並返回該元素所映射的值(也可以是不同類型的值)。具體的映射方式和返回值類型由閉包來指定。
當提供給數組閉包函數後,map方法將返回一個新的數組,數組中包含了與原數組一一對應的映射後的值。
let digitNames = [0: "Zero",1: "One",2: "Two",3: "Three",4: "Four",5: "Five",6: "Six",7: "Seven",8: "Eight",9: "Nine"]
let numbers = [16, 58, 510]
let strings = numbers.map{
(var number) -> String in var output = ""
while number > 0{
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
println(strings)
//[OneSix, FiveEight, FiveOneZero]
//strings 常量被推斷爲字符串類型數組,即String[]
捕獲(Caputure)
閉包可以在其定義的上下文中捕獲常量或變量。即使定義這些常量和變量的原域已經不存在,閉包仍然可以在閉包函數內飲用和修改這些值。
Swift最簡單的閉包形式是嵌套函數,也就是定義在其他函數的函數體內的函數。嵌套函數可以捕獲其外部函數所有的參數以及定義的常量和變量。
func makeIncrementor(forIncrement amount: Int) ->() ->Int{
var runningTotal = 0
func incrementor() ->Int{
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement:10)
println(incrementByTen())
println(incrementByTen())
println(incrementByTen())
//10
//20
//30
注意:如果你閉包分配給一個類實例的屬性,並且該閉包通過指向該實例或其成員來捕獲了該實例。你將創建一個在閉包和實例間的強引用環。Swift使用捕獲列表來打破這種強引用環。
閉包是引用類型
incrementByTen是常量,但是這些常量指向的閉包仍然可以增加其捕獲的變量值,這是因爲函數和閉包都是引用類型。
無論你將函數/閉包賦值給一個常量還是變量,你實際上都是將常量/變量的值設置爲對應函數/閉包的引用。
incrementByTen指向閉包的引用是一個常量,而並非閉包內容本身。
這也就意味着如果您將閉包賦值給了兩個不同的常量/變量,兩個值都會指向同一個閉包。
func makeIncrementor(forIncrement amount: Int) ->() ->Int{
var runningTotal = 0
func incrementor() ->Int{
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement:10)
println(incrementByTen())
println(incrementByTen())
println(incrementByTen())
let incrementByTenAlso = incrementByTen
println(incrementByTen())
//10
//20
//30
//40
無論