閉包是自包含的函數(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