閉包是自包含的功能代碼塊,可以在代碼中使用或者用來作為參數傳值。 在Swift中的閉包與C、OC中的blocks和其它編程語言(如Python)中的lambdas類似。
閉包可以捕獲和存儲上下文中定義的的任何常量和變量的引用。這就是所謂的變量和變量的自封閉, 因此命名為”閉包“(“Closures)”)。Swift還會處理所有捕獲的引用的內存管理。
全局函數和嵌套函數其實就是特殊的閉包。 閉包的形式有三種:
- 全局函數都是閉包,有名字但不能捕獲任何值。
- 嵌套函數都是閉包,且有名字,也能捕獲封閉函數內的值。
- 閉包表達式都是無名閉包,使用輕量級語法,可以根據上下文環境捕獲值。
Swift中的閉包有很多優化的地方:
- 根據上下文推斷參數和返回值類型;
- 從單行表達式閉包中隱式返回(也就是閉包體只有一行代碼,可以省略return);
- 可以使用簡化參數名,如0,1(從0開始,表示第i個參數…);
- 提供了尾隨閉包語法(Trailing closure syntax)。
## 閉包表達式
嵌套函數是非常強大的功能,在一個函數體內嵌套另一個函數。將函數作為參數和返回值也非常有用。這些都是一些特殊情況下的閉包。
閉包表達式是一種簡短的、集中的語法。閉包表達式為了縮短代碼以及優化代碼的閱讀性,提供了幾種語法優化。這里使用數組的排序為大家展示閉包的優化。
### Sort方法
~~~
// 函數做參數,排序
let names = ["陽君", "937447974", "a", "b", "c"]
func backwards(s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sort(backwards)
~~~
從代碼中可以看出,將函數作為參數傳遞對于代碼的閱讀性不是很好,這里就需要閉包表達式對其優化。
### 閉包語法
閉包表達式的結構圖如下所示:

- parameters:閉包接受的參數;
- return type:閉包運行完畢的返回值;
- statements:閉包內的運行代碼。
下面運用閉包表達式代替backwards函數對sort進行優化。
~~~
reversed = names.sort({ (s1: String, s2: String) -> Bool in
return s1 > s2
})
~~~
當要運行的代碼很少時,你也可以將它們寫在一行。
~~~
reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } )
~~~
### 通過上下文判斷類型
在閉包中,我們不必寫參數的類型和返回值的類型,閉包可以通過上下文自動判斷參數類型和返回值類型。
~~~
reversed = names.sort( { s1, s2 in return s1 > s2 } )
~~~
### 從單一表達式隱藏Return
在閉包中,如果運行的內容很少只有一行,則不必寫return,閉包會自動返回。
~~~
reversed = names.sort( { s1, s2 in s1 > s2 } )
~~~
### 速記參數名稱
在閉包中,我們不必命名參數名稱。閉包中的參數可使用`$`去獲得,第一個參數為`$0`,第二個為`$1`。
~~~
reversed = names.sort( { $0 > $1 } )
~~~
### 算子函數
當在閉包中,只有一個表達式,做操作。如在sort中,只有兩個參數做比較操作。閉包支持只輸入>或<做比較。
~~~
reversed = names.sort(>)
~~~
## 尾隨閉包
如果函數需要一個閉包作為參數,且這個參數是最后一個參數。我們又不想在()內寫太多代碼,我們可以運用尾隨閉包。尾隨閉包意味著閉包可以放在函數參數列表外,也就是括號外。
~~~
var reversed = names.sort() { $0 > $1 }
~~~
當尾隨閉包中的參數只有一個時,我們可以省略()。
~~~
reversed = names.sort { $0 > $1 }
~~~
## 捕獲值
閉包可以根據上下文環境捕獲到定義的常量和變量。閉包可以引用和修改這些捕獲到的常量和變量,就算在原來的范圍內定義為常量或者變量已經不再存在。在Swift中閉包的最簡單形式是嵌套函數。
~~~
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print("\(incrementByTen())") // prints "10"
print("\(incrementByTen())") // prints "20"
print("\(incrementByTen())") // prints "30"
~~~
上面的例子介紹了,有個函數makeIncrementer,在函數內有一個嵌套函數incremented。嵌套函數可以通過上下文使用它的外部值runningTotal和amount。
當你想聲明另一個閉包類型時,可以像聲明屬性一樣聲明。
~~~
let incrementBySeven = makeIncrementer(forIncrement: 7)
print("\(incrementBySeven())") // prints "7"
print("\(incrementByTen())") // prints "40"
~~~
可以看出兩個閉包的引用是完全獨立工作的。
## 閉包是引用類型
運用屬性執行的閉包后,我們可以用另一個屬性去引用,對于兩個屬性來說,它們是完全相同的,指向同一個閉包。
~~~
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // prints "50"
~~~
## Noescape關鍵字
@noescape主要用于解決“保留環”問題如下所示,當你調用someFunctionWithEscapingClosure函數時,使用全局屬性,會使用了self,這樣你會發現每次調用閉包時,都會使用捕獲self,這樣容易造成內存泄露的問題,而且閉包中的操作其實是一成不變的,沒有必要每次都訪問。
Swift鑒于這種情況,希望在閉包內不使用self,因此產生了@noescape關鍵字。將@noescape寫入閉包名前。這樣在編寫閉包內代碼時,無須使用self屬性,也避免了保留環問題。
~~~
var completionHandlers: [() -> Void] = []
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
closure()
// completionHandlers.append(closure) //會報錯,closure無法被保存
}
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandler()
completionHandlers.append(completionHandler)
}
class SomeClass {
var x = 10
func doSomething() {
// 內存溢出,保留環問題
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNoescapeClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// prints "200"
completionHandlers.first?()
print(instance.x)
// prints "100"
~~~
## Autoclosures關鍵字
在閉包中,我們調用函數時,是將代碼封裝為一個閉包傳遞給函數。如下所示:
~~~
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func serveCustomer(customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer( { customersInLine.removeAtIndex(0) } )
// prints "Now serving Alex!"
~~~
此時我們會思考,能否讓代碼直接為參數傳遞過去,也就是不用{}包含代碼。@autoclosure關鍵字可以幫助你完成這種機制。
~~~
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serveCustomer2(@autoclosure customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
// 閉包作為參數
serveCustomer2(customersInLine.removeAtIndex(0))
// prints "Now serving Ewa!"
~~~
使用了@autoclosure默認也是使用@noescape,如果你只想使用autoclosure的特性,不想使用noescape的特性,你可以使用escape關鍵字,如下所示:
~~~
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
//autoclosure和escaping一起用
func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
customerProviders.append(customerProvider)
}
// 添加閉包,并且閉包此時為參數
collectCustomerProviders(customersInLine.removeAtIndex(0))
collectCustomerProviders(customersInLine.removeAtIndex(0))
//循環使用閉包
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// prints "Now serving Barry!"
// prints "Now serving Daniella!"
~~~
## 其他
### 參考資料
[The Swift Programming Language (Swift 2.1)](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html)
### 文檔修改記錄
| 時間 | 描述 |
|-----|-----|
| 2015-10-28 | 根據 [The Swift Programming Language (Swift 2.1)](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html)中的Closures總結 |
版權所有:[http://blog.csdn.net/y550918116j](http://blog.csdn.net/y550918116j)
- 前言
- Swift函數
- Swift閉包(Closures)
- Swift枚舉(Enumerations)
- Swift類和結構體(Classes and Structures)
- Swift屬性(Properties)
- Swift方法(Methods)
- Swift下標(Subscripts)
- Swift繼承(Inheritance)
- Swift初始化(Initialization)
- Swift銷毀(Deinitialization)
- Swift可選鏈(Optional Chaining)
- Swift錯誤處理(Error Handling)
- Swift類型選擇(Type Casting)
- Swift協議(Protocols)
- Swift訪問控制(Access Control)
- Swift高級運算符(Advanced Operators)