@autoclosure(自動閉包) 1:自動閉包,顧名思義是一種自動建立的閉包,用於封裝函數參數的運算式,可以說是一種簡便文法. 2:自動閉包不接受任何參數,被調用時會返回被封裝在其中的運算式的值。 3:自動閉包的好處之二是讓你能夠延遲求值,因為程式碼片段不會被執行直到你調用這個閉包,這樣你就可以控制碼什麼時候執行。 4:含有autoclosure特性的聲明同時也具有noescape的特性,及預設是非逃逸閉包,除非傳遞選擇性參數escaping.如果傳遞了該參數,那麼將可以在閉包之外進行操作閉包,形式為:請使用@autoclosure(escaping)。
下面一起來看一個簡單例子:比如我們有一個方法接受一個閉包,當閉包執行的結果為true的時候進行列印:
func printIfTrue(predicate: ()-> Bool){ if predicate(){ print("the result is true") }}//1直接調用方法printIfTrue { () -> Bool in return 2 > 1}//2閉包在圓括弧內printIfTrue({ return 2 > 1 })//3:使用尾部閉包方式,閉包體在圓括弧之外printIfTrue(){ return 2 > 1 }//4:在 Swift 中對閉包的用法可以進行一些簡化,在這種情況下我們可以省略掉 return,寫成:printIfTrue({ 2 > 1})//5:還可以更近一步,因為這個閉包是最後一個參數,所以可以使用尾隨閉包 (trailing closure) 的方式把大括弧拿出來,然後省略括弧,變成:printIfTrue{2 > 1}
但是不管哪種方式,表達上不太清晰,看起來不舒服。於是@autoclosure就登場了。我們可以改換方法參數,在參數名前面加上@autoclosure關鍵字:
func printIfTrue(@autoclosure predicate: ()-> Bool){ if predicate(){ print("the result is true") }}printIfTrue(2 > 1)//直接進行調用了,Swift 將會把 2 > 1 這個運算式自動轉換為 () -> Bool。這樣我們就得到了一個寫法簡單,表意清楚的式子。
如果有多個閉包,那麼就有優勢了,而@autoclosure是可以修飾任何位置的參數:
func printInformation(@autoclosure predicate1: ()-> Bool,@autoclosure predicate2: ()-> Bool){ if predicate1() && predicate2(){ print("the result is true") }else{ print("the result is false") }}printInformation( 3 > 2, predicate2: 4 > 1)
@noescape和 @escape
對於autoclosure屬性來講,還有2個相關的屬性要瞭解一下。也就是@noescape和@escape。這2個屬性都是用來修飾閉包的。@noescape意思是非逃逸的閉包,而@escape則相反。預設情況下,閉包是@escape的。表示此閉包還可以被其他閉包調用。比如我們常用的非同步作業
func executeAsyncOp(asyncClosure: () -> ()) -> Void { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { asyncClosure() }}
其中asyncClosure在dispatch_async中的閉包中調用,完成非同步操作。因為閉包預設是@escape的,以上代碼是可以啟動並執行。但是當我們在asyncClosure前面加入@noescape屬性時候,編譯器就會報錯:
func executeAsyncOp(@noescape asyncClosure: () -> ()) -> Void { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { asyncClosure() }}error:closure use of @noescape parameter 'asyncClosure' may allow it to escape asyncClosure()
@noescape屬性是在 Swift1.2中引入的,把傳入閉包參數的調用限制在調用的函數體內,對效能有一定的提升,同時將閉包標註為@noescape使你能在閉包中隱式地引用self。
在 Swift標準庫中很多方法,都用了@noescape屬性,比如 Array對應的方法 map,filter和 reduce:
func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]func filter(@noescape includeElement: (Self.Generator.Element) -> Bool) -> [Self.Generator.Element]func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) -> T) -> T
但是注意:@noescape在Swift3中是預設的並且建議移除@noescape。事實上,@noescape在swift3中已經被廢棄,以後都不會用到它了,而@escape改為@escaping。
下面看一下@escaping
Swift中我們可以定義一個接受函數作為參數的函數,而在調用時,使用閉包的方式來傳遞這個參數是常見手段,如下:
func doWork(closure: () -> ()) { closure() print("end")}doWork { print("doWork")}//doWork//end
這種最簡單的形式的閉包其實還預設隱藏了一個假設,那就是參數中closure的內容會在doWork返回前就完成, 由執行結果也可以看出來。也就是說,對於closure的調用是同步行為,那麼如果進行非同步呢。大家應該會猜到結果,非同步不就是立馬返回嘛。
那麼我們改變一下代碼,將closure放到一個Dispatch中去,讓它在doWorkAsync返回後被調用的話,我們就需要在closure的類型前加上 @escaping標記來表明這個閉包是會“逃逸”出該方法的:
func doWorkAsync(closure: @escaping () -> ()) { DispatchQueue.main.async { closure() } print("end")}doWorkAsync { print("doWork")}//end//doWork
在使用閉包調用這個兩個方法時,也會有一些行為的不同。我們知道閉包是可以捕獲其中的變數的。對於doWork參數裡這樣的沒有逃逸行為的閉包,因為閉包的範圍不會超過函數本身,所以我們不需要擔心在閉包內持有self等。而接受@escaping的doWorkAsync則有所不同。由於需要確保閉包內的成員依然有效,如果在閉包內引用了self及其成員的話,Swift將強制我們明確地寫出self。我們可以對比下面的兩個用例的不同之處:
class Person { var name = "Jack" func method1() { doWork { print("name = \(name)") } name = "Rose" } func method2() { doWorkAsync { print("name = \(self.name)") } name = "Rose" } func method3() { doWorkAsync { [weak self] in print("name = \(String(describing: self?.name))") } name = "Rose" }}
顯然,method1中調用者不需要考慮self.name的持有情況,使用起來相當直接。對name的列印輸出的是原始值。而method2中由於閉包可逃逸,Swift 強制我們寫明self,以起到提醒作用,我們就需要考慮self的持有情況。在這個例子中,我們讓閉包持有了self,列印的值是最後對name賦值後的Rose。如果不使用self,出現錯誤:
如果我們不希望在閉包中持有self ,可以使用 [weak self]的方式來處理,就是method3方法
如果你在協議或者父類中定義了一個接受@escaping為參數方法,那麼在實現協議和類型或者是這個父類的子類中,對應的方法也必須被聲明為,否則兩個方法會被認為擁有不同的函數簽名,如:
protocol P { func work(b: @escaping () -> ())}//正常class C: P { func work(b: @escaping () -> ()) { }}
如果我們沒有使用@escaping,出錯:
?。操作符
在Swift中,有一個非常有用的操作符,可以用來快速地對nil進行條件判斷,那就是??。這個操作符可以判斷輸入並在當左側的值是非nil的 Optional 值時返回其 value,當左側是nil時返回右側的值,比如:
var level: Int?var startLevel = 1var currentLevel = level ?? startLevel
上面例子結果為1,例子中我們並沒有設定過level,因此最後startLevel被賦值為currentLevel。??操作符其實有兩種形式,如下:
func ??<T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?func ??<T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
在這裡我們的輸入滿足的是後者,雖然表面上看startLevel只是一個Int,但是其實在使用時它被自動封裝成了()->Int,我們可能簡單猜測一下內部實現,如下:
func ??<T>(optinal: T?, defaultValue: @autoclosure () -> T) -> T { switch optinal { case .some(let value): return value case .none: return defaultValue() }}
其實就是對可選值的應用。