1. 程式人生 > >Swift 中的Closures(閉包)詳解

Swift 中的Closures(閉包)詳解

mount light sca ring 需要 line rem sin 代碼

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(閉包)詳解