[iOS筆記]Swift中的閉包(Closures)

閉包是自包含的函數(shù)代碼塊,Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數(shù)比較相似。

閉包表達(dá)式語法的如下一般形式:

 { (parameters) -> returnType in
     statements
}

舉個(gè)例子:

func backwards(s1: String, s2: String) -> Bool {
    return s1 > s2
}
var reversed = names.sort(backwards)

上面例子對(duì)應(yīng)的閉包表達(dá)式版本的代碼:

 reversed = names.sort({ (s1: String, s2: String) -> Bool in
    return s1 > s2
})

1. 閉包特性

1.1 根據(jù)上下文推斷類型(Inferring Type From Context)

任何情況下,通過內(nèi)聯(lián)閉包表達(dá)式構(gòu)造的閉包作為參數(shù)傳遞給函數(shù)或方法時(shí),都可以推斷出閉包的參數(shù)和返回值類型。

 reversed = names.sort( { s1, s2 in return s1 > s2 } )

1.2 單表達(dá)式閉包隱式返回(Implicit Return From Single-Expression Clossures)

單行表達(dá)式閉包可以通過省略 return 關(guān)鍵字來隱式返回單行表達(dá)式的結(jié)果。

reversed = names.sort( { s1, s2 in s1 > s2 } )

1.3 參數(shù)名稱縮寫(Shorthand Argument Names)

Swift 自動(dòng)為內(nèi)聯(lián)閉包提供了參數(shù)名稱縮寫功能,您可以直接通過 $0 , $1 , $2 來順序調(diào)用閉包的參數(shù),以此類推。

reversed = names.sort( { $0 > $1 } )
P.S. 我們可以進(jìn)一步精簡
運(yùn)算符函數(shù)(Operator Functions):

Swift 的 String 類型定義了關(guān)于大于號(hào)( > )的字符串實(shí)現(xiàn),其作為一個(gè)函數(shù)接受兩個(gè) String 類型的參數(shù)并返回 Bool 類型的值。

reversed = names.sort(>)

2. 尾隨閉包(Trailing Closures)

如果需要將一個(gè)很長的閉包表達(dá)式作為最后一個(gè)參數(shù)傳遞給函數(shù),可以使用尾隨閉包來增強(qiáng)函數(shù)的可讀性。

//函數(shù)聲明
func someFunctionThatTakesAClosure(closure: () -> Void) { 
    // 函數(shù)體部分
}

// 以下是不使用尾隨閉包進(jìn)行函數(shù)調(diào)用
someFunctionThatTakesAClosure({
    // 閉包主體部分 
})

// 以下是使用尾隨閉包進(jìn)行函數(shù)調(diào)用 
someFunctionThatTakesAClosure() {
    // 閉包主體部分 
}
//之前的例子可以寫成:
reversed = names.sort() { $0 > $1 }

//如果函數(shù)只需要閉包表達(dá)式一個(gè)參數(shù),當(dāng)使用尾隨閉包時(shí),甚至可以把 () 省略掉:
reversed = names.sort { $0 > $1 }

3. 捕獲(capturing)

閉包可以捕獲和存儲(chǔ)其所在上下文中任意常量和變量的引用。
Swift 會(huì)管理在捕獲過程中涉及到的所有內(nèi)存操作,包括釋放不再需要的變量。

即使定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值。

Swift 中,可以捕獲值的閉包的最簡單形式是嵌套函數(shù),也就是定義在其他函數(shù)的函數(shù)體內(nèi)的函數(shù)。
嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量。

func makeIncrementor(forIncrement amount: Int) -> () -> Int {
     var runningTotal = 0
     //聲明內(nèi)嵌函數(shù)
     func incrementor() -> Int { 
         runningTotal += amount //內(nèi)嵌函數(shù)引用了外部函數(shù)的amount和runningTotal變量
         return runningTotal
     }
     return incrementor
 }

let incrementByTen = makeIncrementor(forIncrement: 10)

incrementByTen() // 返回的值為10
incrementByTen() // 返回的值為20
incrementByTen() // 返回的值為30

P.S 上面的方法調(diào)用可以引申理解為makeIncrementor函數(shù)體是一個(gè)class,
而incrementor是class中的函數(shù),incrementor函數(shù)引用了class中的屬性amount和runningTotal

如果創(chuàng)建了另一個(gè) incrementor ,它會(huì)有屬于它自己的一個(gè)全新、獨(dú)立的 runningTotal 變量的引用:

let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven() // 返回的值為7

4. 閉包是引用類型(Closures Are Reference Types)

上面的例子中, incrementBySeven 和 incrementByTen 是常量,但是這些常量指向的閉包仍然可以增加其捕獲的變量的值。這是因?yàn)楹瘮?shù)和閉包都是引用類型。
無論將函數(shù)或閉包賦值給一個(gè)常量還是變量,實(shí)際上都是將常量或變量的值設(shè)置為對(duì)應(yīng)函數(shù)或閉包的引 用。上面的例子中,指向閉包的引用 incrementByTen 是一個(gè)常量,而并非閉包內(nèi)容本身。

這也意味著如果將閉包賦值給了兩個(gè)不同的常量或變量,兩個(gè)值都會(huì)指向同一個(gè)閉包!

5. 非逃逸閉包(Nonescaping Closures)

當(dāng)一個(gè)閉包作為參數(shù)傳到一個(gè)函數(shù)中,但是這個(gè)閉包在函數(shù)返回之后才被執(zhí)行,我們稱該閉包從函數(shù)中逃逸。
你可以在參數(shù)名之前標(biāo)注 @noescape ,用來指明這個(gè)閉包是不允許“逃逸”出這個(gè)函數(shù)的。
將閉包標(biāo)注 @noescape 能使編譯器知道這個(gè)閉包的生命周期(閉包只能在函數(shù)體中被執(zhí)行,不能脫離函數(shù)體執(zhí)行,所以編譯器明確知道運(yùn)行時(shí)的上下文),從而可以進(jìn)行一些比較激進(jìn)的優(yōu)化。

func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
    closure()
}

//函數(shù)接受的閉包被添加到一個(gè)函數(shù)外定義的數(shù)組中
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
    completionHandlers.append(completionHandler)
}

class SomeClass {
    var x = 10
    func doSomething() {
        //將閉包標(biāo)注為 @noescape 使能在閉包中隱式地引用 self
        someFunctionWithEscapingClosure { self.x = 100 } 
        someFunctionWithNoescapeClosure { x = 200 }
    }
}

let instance = SomeClass()

instance.doSomething()
print(instance.x) // prints "200"

completionHandlers.first?()
print(instance.x) // prints "100"

6. 自動(dòng)閉包(Autoclosures)

這種閉包不接受任何參數(shù),當(dāng)它被調(diào)用的時(shí)候,會(huì)返回被包裝在其中的表達(dá)式的值。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// customerProvider 的類型不是 String ,而是 () -> String 
let customerProvider = { customersInLine.removeAtIndex(0) }

print("Now serving \(customerProvider())!") // prints "Now serving Chris!"
print(customersInLine.count)   // prints "4"
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serveCustomer(customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serveCustomer( { customersInLine.removeAtIndex(0) } )
// prints "Now serving Alex!"


使用@autoclosure
func serveCustomer(@autoclosure customerProvider: () -> String) {
     print("Now serving \(customerProvider())!")
}

serveCustomer(customersInLine.removeAtIndex(0))
 // prints "Now serving Ewa!"

@autoclosure特性暗含了@noescape 特性,如果你想讓這個(gè)閉包可以“逃逸”,則應(yīng)該使用@autoclosure(escaping) 特性.

參考資料:The Swift Programming Language

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容