Swift 中的Closures(閉包)詳解
Swift 中的Closures(閉包)詳解
在Swift沒有發布之前,所有人使用OC語言編寫Cocoa上的程序,而其中經常被人們討論的其中之一 -- Block 一直備受大家的喜愛。在Swift中,同樣有這樣的一個角色,用於當開發者需要異步執行的之後使用的一種語法 - Closure。中文翻譯為閉包。
閉包出了可以進行異步執行之外,它的完整使用還依賴閉包本身的變量、常量的捕獲。閉包捕獲並存儲對它們定義的上下文中的任何常量和變量的引用,這也就意味著,你可以在任何時候異步執行閉包的時候獲取之前的所有的環境變量。而實際上,閉包類似於Swift 中的匿名函數,在上一篇文章中,介紹了高階函數和嵌套函數,它們和閉包有者不可分割的一些聯系。比如,最簡單的閉包起始就是一個高階函數,只是在閉包做為參數變量的時候,閉包是匿名、書寫時實現。當對於一般的高階函數,閉包更輕量級。
本文介紹幾種閉包的形式,以及一些閉包的特性。
一、閉包的基本形式
這是一個最基本的閉包的形式:
{ (parameters) -> return type in statements }
閉包中,包含三要素: 參數,返回類型,閉包體。 其中參數和返回類型可以忽略, 但是一個閉包體必需存在,實際上就算在閉包體裏面,什麽都不寫,閉包體本身還是以不執行任何代碼的形式存在。
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
這是一個高階函數,同時,也是一個閉包的基本使用。包含了 參數及類型,返回值類型,閉包體。
我們可以簡寫閉包的形式,采用內聯的方式書寫:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
在閉包中,因為包含了上下文的變量和常量的引用,並做了類型推斷,所以,實際上,對於閉包的參數來說,類型是固定的,當然返回的類型也是固定的,swift允許開發者書寫時省略。就像下面這樣:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
如果閉包是的閉包體是單個表達式(只有一條執行語句)的時候,甚至可以將return都省略
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
另外swift的閉包又一個特性:Swift自動為內聯閉包提供簡寫參數名稱,可用於通過名稱$ 0,$ 1,$ 2等引用閉包參數的值。
如果在閉包表達式中使用這些簡寫參數名稱,則可以從其定義中省略閉包的參數列表,並根據預期的函數類型推斷速記參數名稱的數量和類型。 in關鍵字也可以省略,因為閉包表達式完全由其主體組成:
reversedNames = names.sorted(by: { $0 > $1 } )
這樣還不夠完美,如果將” > “符號重載,使 $0 > $1 直接用 > 代替是不是更加簡潔呢? 當然可以,事實上,swift中的 > 已經被定義了 > 指代兩個Swift string之間的比較,並返回一個 Bool值。那麽我們的最終版本應該是這樣的:
reversedNames = names.sorted(by: >)
二、尾隨閉包
如果你定義了一個閉包,將之作為一個函數的最後的一個參數,並且,由於之前還包含了其他的參數,導致這個函數變得很長,你可以使用一種簡短的方式書寫閉包。像下面這樣的函數:
func someFunctionThatTakesAClosure(closure: () -> Void) { // function body goes here }
如果我們調用的使用,應該是這樣的:
someFunctionThatTakesAClosure(closure: { // closure‘s body goes here })
像這樣的情況,我們考慮它構成了尾隨閉包,對於尾隨閉包,有另一中更加簡便的方式書寫:
someFunctionThatTakesAClosure() { // trailing closure‘s body goes here }
註意,這時候,閉包的標簽 closure 應該被省略。
這樣的情況,我們可以寫出閉包的最終版本的特別版本:
reversedNames = names.sorted(>)
尾隨閉包使用在一個長度很長的閉包下,效果更好。你可以使用這個技巧寫出很簡短優雅的代碼。
三、閉包捕獲值
之前其實已經介紹了,閉包的本質類似於一個嵌套函數,它具有和嵌套函數一樣能力:獲取局部的變量和常量,在一個閉包中:它可以獲取到外部函數所有參數,和外部函數內定義的任何的常量和變量。
這裏有個嵌套的函數,他和閉包的作用一樣:
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer }
incrementer 可以獲取到amount和runingTotal的值來使用。
調用:
let incrementByTen = makeIncrementer(forIncrement: 10) //綁定引用, 保證用完不會消失。
incrementByTen() // returns a value of 10 incrementByTen() // returns a value of 20 incrementByTen() // returns a value of 30
incrementer 這個嵌套的函數一值在改變runningTotal的值,同時接收不斷變化的amount。
如果換一個函數變量引用,那麽值則會被刷新:
let incrementBySeven = makeIncrementer(forIncrement: 7) incrementBySeven() // returns a value of 7
閉包的引用具有關聯性。和一般的變量一樣:
let alsoIncrementByTen = incrementByTen alsoIncrementByTen() // returns a value of 50
四、逃逸閉包
在網絡請求的過程中,大部分時候都是異步操作的,一般的閉包,雖然已經定義了閉包的閉包體(閉包實現),但是在請求回調的時候,閉包可能不會被調用。原因是可能被釋放, 或者可能被修改。
如果需要保證做到異步的調用,那麽需要在閉包的參數前加 @escaping標記。
添加@escaping標記的閉包,意味著,如果需要訪問自身的自身的變量的時候,必須在閉包內包含自身的引用(或者自身的屬性、變量等)。
下面是兩種是否帶標簽的閉包的對比:
var completionHandlers: [() -> Void] = [] func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) }
func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() }
兩者調用:
class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } someFunctionWithNonescapingClosure { x = 200 } } }
默認的閉包都是 非逃逸閉包。
五、Autoclosures 自動閉包
自動閉包的意思是當閉包做為函數的參數的時候,對於閉包的內容執行的時機取決於誰。 普通的閉包,將會在讀取閉包的時候執行,而自動閉包,在讀取的時候,將閉包做為其參數類型處理,只有等到執行閉包的時候才會處理閉包內的內容。
// customersInLine is ["Ewa", "Barry", "Daniella"] func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } serve(customer: customersInLine.remove(at: 0)) // Prints "Now serving Ewa!"
在執行之前,customerProvider被當作一個 () -> String 處理,而不會涉及到閉包體內部的處理。 如果不使用@autoclosure,那麽函數處理閉包體內的內容。
日常的開發可以使用:
public func Dlog( item:@autoclosure ()->Any){ #if DEBUG print(item()) #else #endif }
默認的自動閉包是 非逃逸閉包,如果想變成逃逸閉包,可以如下:
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
Swift 中的Closures(閉包)詳解