Swift之自動閉包(@autoclosure、@noescape、@escape、??)

來源:互聯網
上載者:User

@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()    }}
其實就是對可選值的應用。



相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.