目錄
- 版本
- 閉包的定義及使用
- 尾隨閉包
- 逃逸閉包
版本
Xcode 11.0
Swift 5.1
閉包的定義及使用
1. 概念
閉包是自包含的函數(shù)代碼塊,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語(yǔ)言中的匿名函數(shù)(Lambdas)比較相似。
其實(shí)全局函數(shù)和嵌套函數(shù)實(shí)際上也是特殊的閉包,區(qū)別如下:
- 全局函數(shù)是一個(gè)有名字但不會(huì)捕獲任何值的閉包
- 嵌套函數(shù)是一個(gè)有名字并可以捕獲其封閉函數(shù)域內(nèi)值的閉包
- 閉包表達(dá)式是一個(gè)利用輕量級(jí)語(yǔ)法所寫(xiě)的可以捕獲其上下文中變量或常量值的匿名閉包
關(guān)于捕獲值后文有介紹
2. 定義 (表達(dá)式)
在討論閉包的定義之前, 我們先來(lái)回顧一下全局函數(shù)和嵌套函數(shù).
// 函數(shù)表達(dá)式
func 函數(shù)名(參數(shù)列表) -> 返回類(lèi)型 {
// 函數(shù)體
}
如上表達(dá)式, 定義在全局域中則稱為全局函數(shù), 定義在某個(gè)函數(shù)體中則稱為嵌套函數(shù).
下面來(lái)看看閉包的表達(dá)式是如何定義的:
// 閉包表達(dá)式
{ (參數(shù)列表) -> 返回值類(lèi)型 in
// 閉包體 (可執(zhí)行代碼語(yǔ)句)
}
對(duì)比函數(shù)和閉包的表達(dá)式, 有以下不同點(diǎn):
- 函數(shù)有名稱; 閉包沒(méi)有名稱.
- 函數(shù)大括號(hào)在返回類(lèi)型后面, 包裹了函數(shù)體; 閉包大括號(hào)則包裹了所有 參數(shù) / 返回值 / in關(guān)鍵字 / 閉包體.
- 閉包的閉包體處在 in 關(guān)鍵字后面區(qū)域
- 閉包表達(dá)式參數(shù)可以是 in-out 參數(shù),但不能設(shè)定默認(rèn)值。
3. 使用 (實(shí)例)
閉包實(shí)例:
// 定義一個(gè)閉包, 并賦值給一個(gè)常量 maxValue
// 或者說(shuō)定義一個(gè)常量 maxValue 指向后面這個(gè)閉包
let maxValue: (Int, Int) -> Int = { (x: Int, y: Int) -> Int in
return x > y ? x : y
}
// 使用 maxValue 來(lái)調(diào)用閉包
print(maxValue(11, 22))
// 打印 22
4. 上下文推斷 和 隱式返回
由于Swift可以推斷參數(shù)和返回值類(lèi)型, 并且單行表達(dá)式閉包可以通過(guò)省略 return 關(guān)鍵字來(lái)隱式返回單行表達(dá)式的結(jié)果, 所以上面的代碼又可以簡(jiǎn)寫(xiě)為:
// 定義
let maxValue: (Int, Int) -> Int = { x, y in x > y ? x : y }
// 使用 maxValue 來(lái)調(diào)用閉包
print(maxValue(11, 22))
// 打印 22
5. 參數(shù)名稱縮寫(xiě)
Swift 自動(dòng)為內(nèi)聯(lián)閉包提供了參數(shù)名稱縮寫(xiě)功能,你可以直接通過(guò) $0,$1,$2 來(lái)順序調(diào)用閉包的參數(shù),以此類(lèi)推。
// 參數(shù)名稱縮寫(xiě)
// 定義一個(gè)閉包, 并賦值給一個(gè)常量 add
let add: (Int, Int) -> Int = { $0 + $1 }
// 使用 add 來(lái)調(diào)用閉包
print(add(11, 22))
// 打印 33
6. 捕獲值
類(lèi)似于嵌套函數(shù), 閉包也可以捕獲外部的值.
閉包可以在其被定義的上下文中捕獲常量或變量。即使定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值。
// 捕獲值
var aa = 10
let setValue: () -> () = {
aa = 88
}
// 調(diào)用閉包 (無(wú)參數(shù)無(wú)返回值)
setValue()
print(aa)
// 打印 88
閉包作為函數(shù)參數(shù)
定義一個(gè)加法函數(shù), 傳入三個(gè)參數(shù): aClosure: (Int, Int) -> Int 和 x: Int 和 y: Int, 返回值是 Int 類(lèi)型. 閉包也是傳入兩個(gè)參數(shù) Int 返回一個(gè) Int 值, 在函數(shù)體內(nèi)返回閉包的返回值.
代碼如下:
// 定義函數(shù)
func doAdd(aClosure: (Int, Int) -> Int, x: Int, y: Int) -> Int {
return aClosure(x, y) // 傳入函數(shù)的x/y值, 返回閉包的返回值
}
// 調(diào)用函數(shù), 在這里寫(xiě)閉包體
let ret = doAdd(aClosure: { (aa, bb) -> Int in
return aa + bb // 閉包體部分: 接收函數(shù)的x/y值進(jìn)行相加
}, x: 10, y: 15)
print(ret)
// 打印 25
尾隨閉包
上面的例子中, 在函數(shù)中傳入閉包當(dāng)做參數(shù), 這種寫(xiě)法可讀性不強(qiáng). 下面我們來(lái)介紹尾隨閉包.
顧名思義, 尾隨閉包就是放在最后面的閉包。進(jìn)一步說(shuō), 尾隨閉包是一個(gè)書(shū)寫(xiě)在函數(shù)圓括號(hào)之后的閉包表達(dá)式,函數(shù)支持將其作為最后一個(gè)參數(shù)調(diào)用。
上面的相加函數(shù)使用尾隨閉包, 加強(qiáng)可讀性:
// 定義函數(shù) (三個(gè)形參:x,y和閉包aClosure; 一個(gè)返回值Int類(lèi)型)
func doAdd(x: Int, y: Int, aClosure: (Int, Int) -> Int) -> Int {
return aClosure(x, y)
}
// 調(diào)用函數(shù), 此時(shí)閉包體已經(jīng)移到()外邊
let ret = doAdd(x: 10, y: 20) { (aa, bb) -> Int in
return aa + bb
}
print(ret)
// 打印 30
注意
必須將閉包作為函數(shù)的最后一個(gè)參數(shù)才能成為尾隨閉包.
逃逸閉包
假如我們將一個(gè)閉包傳入函數(shù)作為參數(shù), 然后異步調(diào)用這個(gè)閉包:
import Foundation
// 定義函數(shù)
func doNothing(aClosure: () -> ()) {
DispatchQueue.global().async {
aClosure() // 異步調(diào)用閉包
}
}
// 調(diào)用函數(shù)
doNothing {
print("調(diào)用了閉包")
}
// 編譯報(bào)錯(cuò)
此時(shí)編譯的時(shí)候會(huì)報(bào)錯(cuò). 因?yàn)楫惒秸{(diào)用閉包的代碼在doNothing函數(shù)返回之后才執(zhí)行, 而閉包作為一個(gè)局部變量在函數(shù)返回之后就被釋放了, 此時(shí)調(diào)用就會(huì)報(bào)錯(cuò). 逃逸閉包能解決這個(gè)問(wèn)題.
當(dāng)一個(gè)閉包作為參數(shù)傳到一個(gè)函數(shù)中,但是這個(gè)閉包在函數(shù)返回之后才被執(zhí)行,我們稱該閉包從函數(shù)中逃逸。當(dāng)你定義接受閉包作為參數(shù)的函數(shù)時(shí),你可以在參數(shù)名之前標(biāo)注 @escaping,用來(lái)指明這個(gè)閉包是允許“逃逸”出這個(gè)函數(shù)的。
再看剛才的例子, 成功運(yùn)行:
import Foundation
// 定義函數(shù)
func doNothing(aClosure: @escaping () -> ()) {
DispatchQueue.global().async {
aClosure() // 異步調(diào)用閉包
}
}
// 調(diào)用函數(shù)
doNothing {
print("調(diào)用了閉包")
}
// 打印 調(diào)用了閉包
注意
將一個(gè)閉包標(biāo)記為 @escaping 意味著你必須在閉包中顯式地引用 self。