本頁包含內容:
? 閉包表達式
? 尾隨閉包
? 值捕獲
? 閉包是引用類型
? 逃逸閉包
? 自動閉包
閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數(shù)比較相似。
閉包可以捕獲和存儲其所在上下文中任意常量和變量的引用。被稱為包裹常量和變量。 Swift 會為你管理在捕獲過程中涉及到的所有內存操作。
在函數(shù)章節(jié)中介紹的全局和嵌套函數(shù)實際上也是特殊的閉包,閉包采取如下三種形式之一:
? 全局函數(shù)是一個有名字但不會捕獲任何值的閉包
? 嵌套函數(shù)是一個有名字并可以捕獲其封閉函數(shù)域內值的閉包
? 閉包表達式是一個利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包
Swift 的閉包表達式擁有簡潔的風格,并鼓勵在常見場景中進行語法優(yōu)化,主要優(yōu)化如下:
? 利用上下文推斷參數(shù)和返回值類型
? 隱式返回單表達式閉包,即單表達式閉包可以省略 return 關鍵字 ? 參數(shù)名稱縮寫
? 尾隨閉包語法
1、閉包表達式
- 閉包表達式語法
閉包表達式語法有如下的一般形式:
<pre> { (parameters) -> returnType in
statements
}</pre>
閉包表達式參數(shù)可以是 in-out 參數(shù),但不能設定默認值。
也可以使用具名的可變參數(shù)(注:但是如果可變參數(shù)不放在參數(shù)列表的最后一位的話,調用閉包的時時編譯器將報錯。)元組也可以作為參數(shù)和返回值。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
//閉包類型:(String, String)->Bool
var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
閉包的函數(shù)體部分由關鍵字 in 引入。該關鍵字表示閉包的參數(shù)和返回值類型定義已經完成,閉包函數(shù)體即將開始。
由于這個閉包的函數(shù)體部分如此短,以至于可以將其改寫成一行代碼:
<pre>reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
print(reversedNames)</pre>
- 定義一個求和閉包
//閉包類型:(Int,Int)->(Int)
<pre> let add:(Int,Int)->(Int) = {
(a,b) in
return a + b;
}
//執(zhí)行閉包,相當于調用函數(shù)
let result = add(1100, 200);
//打印閉包返回值
print("result=(result)");</pre>
參數(shù)和需執(zhí)行的代碼(code)用 關鍵字“in”隔開,如果閉包沒有參數(shù), “ () in”可以直接省略:
<pre>1、{
(參數(shù)1,參數(shù)2) in
//code
}
2、{
//code
}</pre>
- 根據(jù)上下文推斷類型
因為排序閉包函數(shù)是作為 sorted(by:) 方法的參數(shù)傳入的,Swift 可以推斷其參數(shù)和返回值的類型。
sorted(by:) 方法被一個字符串數(shù)組調用,因此其參數(shù)必須是 (String, String) -> Bool 類型的函數(shù)。
這意味著 (String, String) 和 Bool 類型并不需要作為閉包表達式定義的一部分。
因為所有的類型都可以被正確推斷,返回箭頭( -> )和圍繞在參數(shù)周圍的括號也可以被省略:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
單表達式閉包隱式返回
單行表達式閉包可以通過省略 return 關鍵字來隱式返回單行表達式的結果,如上版本的例子可以改寫為:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
在這個例子中,sorted(by:) 方法的參數(shù)類型明確了閉包必須返回一個 Bool 類型值。
因為閉包函數(shù)體只包含 了一個單一表達式( s1 > s2 ),該表達式返回 Bool 類型值,因此這里沒有歧義, return 關鍵字可以省略。-
參數(shù)名稱縮寫
Swift 自動為內聯(lián)閉包提供了參數(shù)名稱縮寫功能,你可以直接通過 $0 , $1 , $2 來順序調用閉包的參數(shù),以此類推。
如果你在閉包表達式中使用參數(shù)名稱縮寫,你可以在閉包定義中省略參數(shù)列表,并且對應參數(shù)名稱縮寫的類型會通過函數(shù)類型進行推斷。
in 關鍵字也同樣可以被省略,因為此時閉包表達式完全由閉包函數(shù)體構成:
reversedNames = names.sorted(by: { $0 > $1 } )
在這個例子中,$0 和 $1表示閉包中第一個和第二個 String 類型的參數(shù)。你也可以用關鍵字typealias先聲明一個閉包的數(shù)據(jù)類型:
聲明一個閉包類型 AddBlock
<pre>typealias AddBlock = (Int,Int)->(Int);
let add:AddBlock = {
(a,b) in
return a + b;
}
let result = add(1100, 200);
print("result=(result)");</pre> -
運算符方法
Swift 的 String 類型定義了關于大于號(>)的字符串實現(xiàn),其作為一個函數(shù)接受兩個 String 類型的參數(shù)并返回 Bool 類型的值。
而這正好與sorted(by:) 方法的參數(shù)需要的函數(shù)類型相符合。
因此,你可以簡單地傳遞一個大于號,Swift 可以自動推斷你想使用大于號的字符串函數(shù)實現(xiàn):
reversedNames = names.sorted(by: >)
print(reversedNames);
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]
2、尾隨閉包
如果你需要將一個很長的閉包表達式作為最后一個參數(shù)傳遞給函數(shù),可以使用尾隨閉包來增強函數(shù)的可讀性。
尾隨閉包是一個書寫在函數(shù)括號之后的閉包表達式,函數(shù)支持將其作為最后一個參數(shù)調用。
在使用尾隨閉包時,你不用寫出它的參數(shù)標簽:
<pre>func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函數(shù)體部分
}
// 以下是不使用尾隨閉包進行函數(shù)調用
someFunctionThatTakesAClosure(closure: {
// 閉包主體部分
})
// 以下是使用尾隨閉包進行函數(shù)調用
someFunctionThatTakesAClosure() {
// 閉包主體部分
}</pre>
sorted(by:) 方法參數(shù)的字符串排序閉包可以改寫為:
reversedNames = names.sorted() { $0 > $1 }
如果閉包表達式是函數(shù)或方法的唯一參數(shù),則當你使用尾隨閉包時,你甚至可以把 () 省略掉:
reversedNames = names.sorted { $0 > $1 }
當閉包非常長以至于不能在一行中進行書寫時,尾隨閉包變得非常有用
舉例來說,Swift 的 Array 類型有一個 map(:) 方法,這個方法獲取一個閉包表達式作為其唯一參數(shù)。
該閉包函數(shù)會為數(shù)組中的每一個元素調用一次,并返回該元素所映射的值。具體的映射方式和返回值類型由閉包來指定。
當提供給數(shù)組的閉包應用于每個數(shù)組元素后,map(:) 方法將返回一個新的數(shù)組,數(shù)組中包含了與原數(shù)組中的元素一一對應的映射后的值。
下例介紹了如何在 map(_:) 方法中使用尾隨閉包
將 Int 類型數(shù)組 [16, 58, 510] 轉換為包含對應 String 類型的值的數(shù)組
["OneSix", "FiveEight", "FiveOneZero"] :
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
let strings = numbers.map {
(number) ->String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
}while number > 0
return output;
}
print(strings)
閉包表達式在每次被調用的時候創(chuàng)建了一個叫做 output 的字符串并返回。
其使用求余運算符( number % 10 )計算最后一位數(shù)字并利用 digitNames 字典獲取所映射的字符串。這個閉包能夠用于創(chuàng)建任意正整數(shù)的字符 串表示。
閉包簡寫:
1.如果沒有參數(shù), 沒有返回值, in和in之前的東西可以省略
2.如果閉包是函數(shù)的最后一個參數(shù), 可以寫在()后面 -- 尾隨閉包
3.如果只有一個閉包參數(shù), 那么()也可以省略 -- 尾隨閉包
** 3、閉包的用法 **
- (1) 兩個類之間的通信
CustomView類中代碼:
<pre>class CustomView: UIView {
//聲明一個屬性btnClickBlock,type為閉包可選類型
//閉包類型:()->() ,無參數(shù),無返回值
var btnClickBlock:(()->())?
//重寫 init(frame: CGRect)構造函數(shù)
override init(frame: CGRect) {
super.init(frame:frame)
//創(chuàng)建按鈕
let btn = UIButton(frame: CGRect(x: 15, y: 15, width: 80, height: 32))
btn.setTitle("按鈕", for: .normal)
btn.backgroundColor = UIColor.blue
//綁定事件
btn.addTarget(self, action: #selector(CustomView.btnClick), for: .touchDown)
//添加
addSubview(btn)
}
//按鈕點擊事件函數(shù)
func btnClick(){
if self.btnClickBlock != nil {
//點擊按鈕執(zhí)行閉包
//注意:屬性btnClickBlock是可選類型,需要先解包
self.btnClickBlock!()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}</pre>
Controller類中代碼:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//創(chuàng)建CustomView對象
let cutomeView = CustomView(frame: CGRect(x: 50, y: 50, width: 200, height: 200));
//給cutomeView的btnClickBlock閉包屬性賦值
cutomeView.btnClickBlock = {
// () in 無參數(shù)可以省略
//當按鈕被點擊時會執(zhí)行此代碼塊
print("按鈕被點擊");
}
cutomeView.backgroundColor = UIColor.yellow;
//添加到控制器view上
self.view.addSubview(cutomeView)}}
-
(2)異步回調(callBack)
// 定義一個網(wǎng)絡請求函數(shù)// - parameter urlString: 請求接口 String // - parameter succeed: 成功的回調 可選閉包 // - parameter failure: 失敗的回調 可選閉包 func requestData(url:String, success:@escaping ((Any?)->(Void)), failure:((Any?)->(Void))?) { //發(fā)送網(wǎng)絡請求 let session = URLSession.shared; let request = URLRequest(url: URL(string:url)!) let task = session.dataTask(with: request) { (data, respon, error) in if error == nil { //請求成功,執(zhí)行成功的回調,并把數(shù)據(jù)傳遞出去 success(data); }else{ //請求失敗,執(zhí)行失敗的回調,并把錯誤傳遞出去 failure?(error); } } task.resume(); } //調用函數(shù)requestData函數(shù) requestData(url: "http://www.baidu.com", success: { (data) -> (Void) in print("請求成功") }) { (error) -> (Void) in print("請求失敗") }
** 4、解決循環(huán)引用的方式**
方案一:使用weak
weak var weakSelf = self
//調用函數(shù)requestData函數(shù)
requestData(url: "http://www.baidu.com", success: {(data) -> (Void) in
print("請求成功")
weakSelf?.view.backgroundColor = UIColor.blue
}) { (error) -> (Void) in
print("請求失敗")
}方案二:和方案一類型,只是書寫方式更加簡單,可以寫在閉包中,并且在閉包中用到的self都是弱引用
requestData(url: "http://www.baidu.com", success: { weak self -> (Void) in
self?.view.backgroundColor = UIColor.red
print("請求成功")
}) { (error) -> (Void) in
print("請求失敗")
}方案三:使用關鍵字unowned,從行為上來說 unowned 更像OC中的 unsafe_unretained,
// unowned 表示:即使它原來引用的對象被釋放了,仍然會保持對被已經釋放了的對象的一個 "無效的" 引用,它不能是 Optional 值,也不會被指向 nil
requestData(url: "http://www.baidu.com", success: { unowned self -> (Void) in
self.view.backgroundColor = UIColor.red
print("請求成功")
}) { (error) -> (Void) in
print("請求失敗")
}
/*
** 5、逃逸閉包 **
當閉包作為一個參數(shù)傳遞到函數(shù)時,我們知道它一般是用于函數(shù)內部的異步回調,閉包是等異步任務完成以后才調用,而函數(shù)是會很快執(zhí)行完畢并返回的,所以閉包它需要逃逸,以便稍后的回調。
逃逸閉包一般用于異步函數(shù)的回調,比如網(wǎng)絡請求成功的回調和失敗的回調。語法:在函數(shù)的閉包行參前加關鍵字 “@escaping”。
那么沒有出現(xiàn)關鍵字“@escaping”,你可以拉回去看下成功回調或失敗的回調,類型是 “((Any?)->(Void))?”,后面帶了個“?”,這是閉包可選類型,并不是閉包類型,所以無需關鍵字“@escaping”。
假設成功和失敗的回調要弄成閉包類型,而你又要異步使用的話,那就要在形參前面加關鍵字,
*/
func requestDataA(urlString:String,succeed: @escaping (Any?)->(Void),failure:@escaping (Any?)->(Void)){
let request = URLRequest(url: URL(string: urlString)!);
//發(fā)送網(wǎng)絡請求
NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue()) { (_, data, error) in
if error == nil {
//請求成功,執(zhí)行成功的回調,并把數(shù)據(jù)傳遞出去
succeed(data);
}else{
//請求失敗,執(zhí)行失敗的回調,并把錯誤傳遞出去
failure(error);
}
}
}