閉包是可以在你的代碼中被傳遞和飲用的功能性獨立模塊。Swift中的閉包和C以及Objective-C中的Block很像,和其他語言中的匿名函數也很像。
閉包能捕獲和存儲定義在其上下文中的任何常量和變量的飲用,這也就是所謂的閉合並包裹那些常量和變量,因此稱為閉包,Swift能夠為你處理所有關於捕獲內存管理的操作。
在上一篇函數的介紹中 全局和內嵌函數 實際上就是特殊的閉包,閉包符合如下三種形式中的一種
全局函數是一個有名字但不會捕獲任何值的閉包
內嵌函數是一個有名字且能從7其上層函數捕獲值的閉包
閉包表達式是一個輕量級語法所寫的可以捕獲其上下文中常量貨變量值的沒有名字的閉包
Swift 的閉包表達式擁有簡潔的風格,鼓勵在常見場景中實現簡潔,無累贅的語法。常見的優化包括:
利用上下文推斷形式參數和返回值的類型;
單表達式的閉包可以隱式返回;
簡寫實際參數名;
尾隨閉包語法。
Sorted方法
Swift 的標准庫提供了一個叫做 sorted(by:) 的方法,會根據你提供的排序閉包將已知類型的數組的值進行排序。一旦它排序完成, sorted(by:) 方法會返回與原數組類型大小完全相同的一個新數組,該數組的元素是已排序好的。原始數組不會被 sorted(by:) 方法修改。
下面這個閉包表達式的栗子使用 sorted(by:) 方法按字母排序順序來排序一個 String 類型的數組。這是將被排序的初始數組:
let names = ["Chris","Alex","Ewa","Barry","Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2 } var reversedNames = names.sorted(by: backward)
閉包的表達式語法
//閉包的表達式語法 {(parameters) ->(return type) in statements }
閉包表達式語法能夠使用常量形式參數、變量形式參數和輸入輸出形式參數,但不能提供默認值。可變形式參數也能使用,但需要在形式參數列表的最后面使用。元組也可被用來作為形式參數和返回類型。
下面的例子和上面的例子效果是一樣的。
let names = ["Chris","Alex","Ewa","Barry","Daniella"] names.sorted(by: {(s1:String,s2:String) -> Bool in return s1 > s2 })
需要注意的是行內閉包的形式參數類型和返回類型的聲明與 backwards(_:_:) 函數的申明相同。在這兩個方式中,都書寫成 (s1: String, s2: String) -> Bool。總之對於行內閉包表達式來說,形式參數類型和返回類型都應寫在花括號內而不是花括號外面。
閉包的函數整體部分由關鍵字 in 導入,這個關鍵字表示閉包的形式參數類型和返回類型定義已經完成,並且閉包的函數體即將開始。
從語境中推斷類型
因排序閉包為實際參數來傳遞給函數,故 Swift 能推斷它的形式參數類型和返回類型。 sorted(by:) 方法期望它的第二個形式參數是一個 (String, String) -> Bool 類型的函數。這意味着 (String, String)和 Bool 類型不需要被寫成閉包表達式定義中的一部分,因為所有的類型都能被推斷,返回箭頭 ( ->) 和圍繞在形式參數名周圍的括號也能被省略:
names.sorted(by: {s1,s2 in return s1 > s2})
從單表達式閉包隱式返回
單表達式閉包能夠通過從它們的聲明中刪掉 return 關鍵字來隱式返回它們單個表達式的結果,前面的栗子可以寫作:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
尾隨閉包
如果你需要將一個很長的閉包表達式作為函數最后一個實際參數傳遞給函數,使用尾隨閉包將增強函數的可讀性。尾隨閉包是一個被書寫在函數形式參數的括號外面(后面)的閉包表達式:
func someFunctionThatTakesAClosure(closure:()->()){
}
調用
someFunctionThatTakesAClosure { print("heihei") }
具體例子
func loadData(completion:@escaping(_ result:[String])->()){ //耗時操作 DispatchQueue.global().async { print("耗時操作") } let json = ["閉包","傳值","Demo"] //回到主線程更新UI DispatchQueue.main.async { print("json數據 \(json)") completion(json) } } loadData { (result) in print("直接操作 \(result)") }
捕獲值
一個閉包能從上下文中捕獲已被定義的常量或者變量,即使定義這些常量和變量的原作用語已經不存在,閉包仍能夠在其函數體內引用和修改這些值。
在Swift中,一個能捕獲值的閉包最簡單的模型是內嵌函數。例子
func makeIncrementer(forincrementer amount:Int) ->()->Int { var runingTotal = 0 func incrementer()->Int { runingTotal += amount return runingTotal } return incrementer } makeIncrementer(forincrementer: 6)()
incrementer() 函數是沒有任何形式參數, runningTotal 和 amount 不是來自於函數體的內部,而是通過捕獲主函數的 runningTotal 和 amount 把它們內嵌在自身函數內部供使用。當調用 makeIncrementer 結束時通過引用捕獲來確保不會消失,並確保了在下次再次調用 incrementer 時, runningTotal 將繼續增加
注意:作為一種優化 如果一個值沒有改變或者在閉包的外面 Swift可能會使用這個值的拷貝而不是捕獲。
Swift也處理了變量的內存管理操作,當變量不再需要時會被釋放。
逃逸閉包
當閉包作為一個實際參數傳遞給一個函數的時候,我們就說這個閉包逃逸了。因為它可以在函數返回之后被調用。當你聲明一個接受閉包作為形式參數的函數時,你可以在這個形式參數前寫 @escaping來明確閉包時允許逃逸的。
閉包可以逃逸的一種方式是被存儲在定義函數外的變量里。例如
var completionHandlers:[() ->Void] = [] func someFunctionWithEscapingClosure(completionHandler:@escaping ()->Void) { completionHandlers.append(completionHandler) }
函數 someFunctionWithEscapingClosure(_:) 接收一個閉包作為實際參數並且添加它到聲明在函數外部的數組里。如果你不標記函數的形式參數為 @escaping ,你就會遇到編譯時錯誤。
讓閉包 @escaping 意味着你必須在閉包中顯示的引用self 比如在下面的代碼中,傳給someFunctionWithEscapingClosure(_:) 的閉包是一個逃逸閉包,也就是說它需要顯式地引用 self 。相反,傳給 someFunctionWithNonescapingClosure(_:) 的閉包是非逃逸閉包,也就是說它可以引式地引用 self 。
var completionHandlers:[() ->Void] = [] func someFunctionWithEscapingClosure(completionHandler:@escaping ()->Void) { completionHandlers.append(completionHandler) } func someFunctionWithNoescapingClosure(closure:() ->Void) { closure() } class someClass { var x = 10 func doSomething() { someFunctionWithNoescapingClosure { x = 100 } someFunctionWithEscapingClosure { self.x = 200 } } }
自動閉包
自動閉包是一種自動創建的用來把作為實際參數傳遞給函數的表達式打包的閉包 它不接受任何實際參數,並且當他被調用的時候,他會返回內部打包的表達式的值。這個語法的好處在與通過寫普通表達式替代顯示閉包而使你省略包圍函數形式參數的括號。
調用一個帶有自動閉包的函數是很常見的,但實現這類函數就不那么常見了。比如說, assert(condition:message:file:line:) 函數為它的 condition 和 message 形式參數接收一個自動閉包;它的 condition 形式參數只有在調試構建是才評判,而且 message 形式參數只有在 condition 是 false 時才評判。
自動閉包允許你延遲處理,因此閉包內部的代碼直到你調用它的時候才會運行。對於有副作用或者占用資源的代碼來說很有用,因為它可以允許你控制代碼何時才進行求值。下面的代碼展示了閉包如何延遲求值。
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] print(customersInLine) let customerProvider = {customersInLine.remove(at: 0)} print(customersInLine.count)//5 print("now serving \(customerProvider())!") customersInLine.count // 4
盡管 customersInLine 數組的第一個元素以閉包的一部分被移除了,但任務並沒有執行直到閉包被實際調用。如果閉包永遠不被調用,那么閉包里邊的表達式就永遠不會求值。
當你傳一個閉包作為實際參數到函數的時候,你會得到與延遲處理相同的行為。
如果你想要自動閉包允許逃逸,就同時使用 @autoclosure 和 @escaping 標志。
這些只是閉包的一些簡單概念和簡單例子,在實際應用中還要注意它們的內存管理等等。會在后面的與oc中的block對比中進行學習。敬請關注。