前言
Swift中的閉包跟OC中的代碼塊(Block)比較相似,下面學習一下。
1.閉包表達式
-
sort方法
sort
方法能根據提供的閉包函數將已知類型數組中的值進行排序,排序后原數組不會被修改。let names =["Chris", "Alex", "Ewa", "Barry", "Daniella"] func backwards(s1:String, s2:String) -> Bool{ return s1 > s2 } var reve =names.sort(backwards) // reve 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
上面這些代碼本質上只是寫了一個單表達式(a>b),接下來我們利用閉包表達式來構造一個內聯排序閉包
語法:
{ (prameters) -> returnType in
statements
}
上面例子的修改(內聯閉包)
reve = names.sort({(s1: String, s2:String) -> Bool in
return s1 >s2
})
-
根據上下文判斷類型
因為上訴排序閉包函數是作為sort(-:)
方法的參數傳入的,swift可以推斷類型,sort(-:)
方法被字符串數組調用,因此傳進去的參數必須是(String,String)->Bool
類型的函數,這意味著(String, String)
和Bool類型并不需要作為閉包表達式定義的一部分。因為所有的類型都可以被正確推斷,返回箭頭(->
)和圍繞在參數周圍的括號也可以被省略:reve = names.sort({s1,s2 in return s1 > s2})
-
單表達式閉包隱式返回
單行表達式閉包可以通過省略return
關鍵字來隱式返回單行表達式的結果:reve = names.sort({s1,s2 in s1 > s2}) 這個例子,明確表示需要返回`Bool`類型的值,因為只包含單一表達式`s1>s2`。
-
參數名稱縮寫
可直接用$0,$1,$2
來順序調用閉包的參數,reve = names.sort({$0>$1}) $0,$1表示閉包中第一個和第二個`String`類型的參數
-
運算符函數
實際上還有一種更簡短的方式來撰寫上面例子中的閉包表達式。Swift 的String
類型定義了關于大于號>
的字符串實現,其作為一個函數接受兩個String
類型的參數并返回Bool
類型的值。而這正好與sort(_:)
方法的參數需要的函數類型相符合。因此,您可以簡單地傳遞一個大于號,Swift 可以自動推斷出您想使用大于號的字符串函數實現:reve = names.sort(>)
2.尾隨閉包
如果需要將一個很長的閉包表達式作為最后一個參數傳遞給函數,可以使用尾隨閉包來增強函數的可讀性。尾隨閉包是一個書寫在函數括號之后的閉包表達式,函數支持將其作為最后一個參數調用:
func some(close: ()->Void){
函數體部分
}
//不使用尾隨閉包
some({
//閉包主體部分
})
//使用尾隨閉包
some(){
//閉包主體部分
}
例子:
reve = names.sort({$0>$1})
可以改寫為
reve = names.sort(){$0>$1}
如果函數只需要一個閉包表達式一個參數,可以省略()
:
reve = names.sort {$0>$1}
4.捕獲值
閉包可以在其被定義的上下文中捕獲常量貨變量。即使定義這些常量和變量的原作用域已經不存在,閉包仍然可以在閉包函數體內引用和修改這些值。
Swift 中,可以捕獲值的閉包的最簡單形式是嵌套函數,也就是定義在其他函數的函數體內的函數。嵌套函數可以捕獲其外部函數所有的參數以及定義的常量和變量。
舉個例子,這有一個叫做makeIncrementor
的函數,其包含了一個叫做increment or
的嵌套函數。嵌套函數incrementor()
從上下文中捕獲了兩個值,runningTotal
和amount
。捕獲這些值之后,makeIncrementor
將increment or
作為閉包返回。每次調用increment or
時,其會以amount
作為增量增加runningTotal
的值。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
makeIncrementor
函數的返回值類型為()->Int
,即incrementor
,
定義了一個runningTotal的變量,incrementor
函數內部捕獲外部變量。
注意:為了優化,如果一個值是不可變的,Swift 可能會改為捕獲并保存一份對值的拷貝。
Swift 也會負責被捕獲變量的所有內存管理工作,包括釋放不再需要的變量。
let incrementByTen = makeIncrementor(forIncrement: 10)
調用
incrementByTen()
// 返回的值為10
incrementByTen()
// 返回的值為20
incrementByTen()
// 返回的值為30
由此可見,每次運行之后,函數對值的改變是有效的并且持續的。
如果創建一個新的incrementor
,便會有全新的獨立的runningTotal
變量引用:
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
// 返回的值為7
再次調用incrementByTen()
會在原來變量上繼續增加
incrementByTen()
// 返回的值為40
注意:如果您將閉包賦值給一個類實例的屬性,并且該閉包通過訪問該實例或其成員而捕獲了該實例,您將創建一個在閉包和該實例間的循環強引用。Swift 使用捕獲列表來打破這種循環強引用。
5.閉包是引用類型
將閉包賦值給兩個不同的變量貨常量,實際上兩個值是指向同一個閉包
6.非逃逸閉包
當一個閉包作為參數傳到函數中,但是這個閉包在函數返回之后才被執行,稱之為逃逸函數
.可以在閉包前加上@noescape
,指明不允許逃逸閉包,能使編譯器知道這個閉包的生命周期,閉包只能在函數體中被執行,不能脫離函數體執行,所以編譯器明確知道運行時的上下文。
一種能使閉包“逃逸”出函數的方法是,將這個閉包保存在一個函數外部定義的變量中。舉個例子,很多啟動異步操作的函數接受一個閉包參數作為 completion handler。這類函數會在異步操作開始之后立刻返回,但是閉包直到異步操作結束后才會被調用。在這種情況下,閉包需要“逃逸”出函數,因為閉包需要在函數返回之后被調用。例如:、
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandlers.append(completionHandler)
}
注意:將閉包標注為@noescape使你能在閉包中隱式地引用self。