Swift 學習筆記 (閉包)

閉包是能夠在你的代碼中被傳遞和飲用的功能性獨立模塊。Swift中的閉包和C以及Objective-C中的Block很像,和其餘語言中的匿名函數也很像。json

閉包能捕獲和存儲定義在其上下文中的任何常量和變量的飲用,這也就是所謂的閉合幷包裹那些常量和變量,所以稱爲閉包,Swift可以爲你處理全部關於捕獲內存管理的操做。api

在上一篇函數的介紹中 全局和內嵌函數 實際上就是特殊的閉包,閉包符合以下三種形式中的一種數組

全局函數是一個有名字但不會捕獲任何值的閉包閉包

內嵌函數是一個有名字且能從7其上層函數捕獲值的閉包app

閉包表達式是一個輕量級語法所寫的能夠捕獲其上下文中常量貨變量值的沒有名字的閉包async

Swift 的閉包表達式擁有簡潔的風格,鼓勵在常見場景中實現簡潔,無累贅的語法。常見的優化包括:

利用上下文推斷形式參數和返回值的類型;
單表達式的閉包能夠隱式返回;
簡寫實際參數名;
尾隨閉包語法。ide

 

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對比中進行學習。敬請關注。

相關文章
相關標籤/搜索