『閉包』是獨立的代碼塊,可以在代碼中隨意傳遞和使用 。Swift 中的閉包與 Objective-C/C 中的 Block、其他編程語言中的匿名函數(shù)相似。
全局和嵌套函數(shù)實際上也是特殊的閉包。閉包采取如下三種形式之一:
- 全局函數(shù)是一個有名字但不會捕獲任何值的閉包。
- 嵌套函數(shù)是一個有名字并且可以捕獲其封閉函數(shù)域內(nèi)值的閉包。
- 閉包表達(dá)式是一個用輕量語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包。
| 全局函數(shù) | 嵌套函數(shù) | 閉包表達(dá)式 |
|---|---|---|
| 名字但不會捕獲任何值的閉包 | 有名字并且可以捕獲其封閉函數(shù)域內(nèi)值的閉包 | 用輕量語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包 |
閉包表達(dá)式
語法
Swift 中的閉包表達(dá)式很靈活,其標(biāo)準(zhǔn)語法格式如下:
{
??(參數(shù)列表) -> 返回值類型 in
??函數(shù)體代碼
}
其中,參數(shù)列表與函數(shù)中的參數(shù)列表形式一樣,返回值類型類似于函數(shù)中的返回值類型,但不同的是后面有in關(guān)鍵字(分割返回值類型和函數(shù)體代碼)。
示例:
var sum = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
sum(1, 2)
輸出:3
復(fù)制代碼
可以看到上面的閉包表達(dá)式其實和通過 func 定義的函數(shù)很像,也實現(xiàn)同樣的功能 func 定義的函數(shù)
func sumFunc(v1: Int, v2: Int) -> Int {
return v1 + v2
}
sumFunc(v1: 1, v2: 2)
復(fù)制代碼
閉包表達(dá)式的簡寫 Swift 提供了多種閉包簡化寫法
定義函數(shù),接受三個參數(shù),其中第三個參數(shù)為一個函數(shù)類型,函數(shù)內(nèi)調(diào)用第三個參數(shù)(即傳入的那個函數(shù)),并計算傳入的兩個參數(shù)之和
func sumFunc(v1: Int, v2: Int, sum: (Int, Int) -> Int) {
print(sum(v1, v2))
}
復(fù)制代碼
調(diào)用sumFunc,傳入第三個參數(shù),通過標(biāo)準(zhǔn)閉包表達(dá)式傳入
sumFunc(v1: 1, v2: 2, sum: {
(number1: Int, number2: Int) -> Int in
return number1 + number2
})
最終打?。?
復(fù)制代碼
類型推斷簡化
由于在調(diào)用函數(shù)傳入閉包表達(dá)式參數(shù)時,形參的sum參數(shù)可以推斷入?yún)⒑头祷刂凳?Int,則可以簡化
sumFunc(v1: 1, v2: 2, sum: {
number1, number2 in
return number1 + number2
})
輸出:3
復(fù)制代碼
隱藏return關(guān)鍵字(單表達(dá)式閉包隱式返回)
如果在閉包內(nèi)部語句組只有一條語句,如return a + b等,那么這種語句都是返回語句。前面的關(guān)鍵字return可以省略,省略形式如下: {number1, number1 in number1 + number1 } 所以:
sumFunc(v1: 1, v2: 2, sum: {
number1, number2 in
number1 + number2
})
輸出:3
復(fù)制代碼
需要注意的是,省略的前提是閉包中只有一條 return 語句:number1 + number2。下面這樣有多條語句是不允許的。
縮寫參數(shù)名稱
Swift提供了參數(shù)名稱縮寫功能,我們可以用0、0、0、1、2來表示調(diào)用閉包中參數(shù),2來表示調(diào)用閉包中參數(shù),2來表示調(diào)用閉包中參數(shù),0指代第一個參數(shù),1指代第二個參數(shù),1指代第二個參數(shù),1指代第二個參數(shù),2指代第三個參數(shù),以此類推n+1指代第n個參數(shù)。 使用參數(shù)名稱縮寫,還可以在閉包中省略參數(shù)列表的定義,Swift能夠推斷出這些縮寫參數(shù)的類型。此外,in關(guān)鍵字也可以省略。參數(shù)名稱縮寫之后如下所示: {0 + $1}
sumFunc(v1: 1, v2: 2, sum: { $0 + $1 })
輸出:3
復(fù)制代碼
運算符方法 實際上還有一種 更簡短 的方式來編寫上面例子中的閉包表達(dá)式。Swift 的 Int 類型 的 + 可以直接縮寫為 + 進(jìn)一步縮寫后:
sumFunc(v1: 1, v2: 2, sum: +)
輸出:3
復(fù)制代碼
尾隨閉包
如果將一個很長的閉包表達(dá)式做為函數(shù)的最后一個參數(shù),可以使用尾隨閉包來增強函數(shù)的可讀性。
尾隨閉包是一個書寫在函數(shù)括號之后的閉包表達(dá)式,函數(shù)支持將其作為最后一個參數(shù)調(diào)用。在使用尾隨閉包時,不用寫出它的參數(shù)標(biāo)簽
定義函數(shù):
func funcClosure(closure: () -> Void) {
// 函數(shù)體部分
}
// 不使用尾隨閉包進(jìn)行函數(shù)調(diào)用
funcClosure(closure: {
// 閉包主體部分
})
// 使用尾隨閉包進(jìn)行函數(shù)調(diào)用
funcClosure() {
// 閉包主體部分
}
復(fù)制代碼
同樣以上面 sumFunc 為例:
sumFunc(v1: 1, v2: 2) {
$0 + $1
}
復(fù)制代碼
如果閉包表達(dá)式是函數(shù)或方法的唯一參數(shù),則當(dāng)你使用尾隨閉包時,你甚至可以把 () 省略掉:
func addFunc(add: (Int, Int) -> Int) {
print(add(1, 2))
}
addFunc {
$0 + $1
}
復(fù)制代碼
閉包值捕獲
閉包可以在其被定義的上下文中捕獲常量或變量。即使定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍然可以在閉包函數(shù)體內(nèi)引用和修改這些值。
Swift 中,可以捕獲值的閉包的最簡單形式是嵌套函數(shù),也就是定義在其他函數(shù)的函數(shù)體內(nèi)的函數(shù)。嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量。
示例 函數(shù) summationFunc(一個參數(shù),返回值為一個函數(shù)) 內(nèi)部嵌套函數(shù) summation(無參,返回值為整型)。
嵌套函數(shù) summation() 捕獲了兩個值,number 和 result。捕獲這兩個值之后,外層函數(shù) summationFunc 將 嵌套函數(shù) summation 作為閉包返回。每次調(diào)用 summation 時,其會以 number 作為增量增加到 result。
ps;iOS開發(fā)交流技術(shù)群:歡迎你的加入,不管你是大牛還是小白都?xì)g迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗,討論技術(shù), 大家一起交流學(xué)習(xí)成長
func summationFunc(number: Int) -> () -> Int {
var result = 2
func summation() -> Int {
result += number
return result
}
return summation
}
let function = summationFunc(number: 10)
let v1 = function()
let v2 = function()
let v3 = function()
print("v1=\(v1), v2=\(v2), v3=\(v3)")
let function2 = summationFunc(number: 10)
let v4 = function2()
let v5 = function2()
let v6 = function2()
print("v4=\(v4), v5=\(v5), v6=\(v6)")
復(fù)制代碼
輸出信息:
v1=12, v2=22, v3=32
v4=12, v5=22, v6=32
復(fù)制代碼
ps;iOS開發(fā)交流技術(shù)群:歡迎你的加入,不管你是大牛還是小白都?xì)g迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗,討論技術(shù), 大家一起交流學(xué)習(xí)成長
可以看到程序可以正常運行,且function 和 function2 每次調(diào)用會分別累加。 我們知道一般情況下 函數(shù)的形參和函數(shù)中的局部變量存儲在棧取,在函數(shù)調(diào)用結(jié)束后就會被銷毀。而此處的函數(shù) summationFunc 和我們想象的不一樣。 對于嵌套函數(shù):
func summation() -> Int {
result += number
return result
}
復(fù)制代碼
函數(shù)沒有任何參數(shù),函數(shù)體內(nèi)卻訪問了的 result 和 number 兩個變量。 這是因為它捕獲了 result 和 number 變量的引用。捕獲引用保證了 result 和 number 變量在調(diào)用完 summationFunc 后不會消失,并且保證了在下一次執(zhí)行 summation 函數(shù)時,兩個變量依舊存在。
斷點調(diào)試可以發(fā)現(xiàn)該閉包調(diào)用了 swift_allocObject,創(chuàng)建了對象存儲在堆中,也就是說在堆中創(chuàng)建了一個對象用于存儲 result。閉包準(zhǔn)確的數(shù)應(yīng)該描述為函數(shù)連同其捕獲的變量(或常量)的組合。
捕獲上下文變量(常量)的時機:
func summationFunc(number: Int) -> () -> Int {
var result = 2
func summation() -> Int {
result += number
return result
}
result = 200
return summation
}
let function = summationFunc(number: 10)
let v1 = function()
let v2 = function()
print("v1=\(v1), v2=\(v2)")
復(fù)制代碼
輸出:
v1=30, v2=40
復(fù)制代碼
debug 可以看到,在返回之前進(jìn)行捕獲,且捕獲了兩次(每賦值一次就捕獲一次),且最終存儲的是最后一次賦的值 閉包是引用類型
上面的例子中,function 和 function1 都是常量,但是這些常量指向的閉包仍然可以增加其捕獲的變量的值。這是因為函數(shù)和閉包都是引用類型。
無論你將函數(shù)或閉包賦值給一個常量還是變量,你實際上都是將常量或變量的值設(shè)置為對應(yīng)函數(shù)或閉包的引用。上面的例子中,指向閉包的引用 function(或function1) 是一個常量,而并非閉包內(nèi)容本身。 這也意味著如果你將閉包賦值給了兩個不同的常量或變量,兩個值都會指向同一個閉包: 例如將上述 function 賦值給 testFunction,則繼續(xù)調(diào)用
func summationFunc(number: Int) -> () -> Int {
var result = 2
func summation() -> Int {
result += number
return result
}
return summation
}
let function = summationFunc(number: 10)
let v1 = function()
let v2 = function()
let v3 = function()
print("v1=\(v1), v2=\(v2), v3=\(v3)")
let testFunction = function
let t1 = function()
let t2 = function()
let t3 = function()
print("t1=\(t1), t2=\(t2), t3=\(t3)")
復(fù)制代碼
打印信息:
v1=12, v2=22, v3=32
t1=42, t2=52, t3=62