閉包表達式(Closure Expression)
可以通過func定義一個函數,也可以通過閉包表達式定義一個函數
- func函數
- 閉包定義函數
閉包表達式調用可以直接省略參數名
閉包定義函數也可以寫爲:
- 閉包表達式的結構
閉包表達式的簡寫
尾隨閉包
如果將一個很長的閉包表達式作爲函數的最後一個實參,使用尾隨閉包可以增強函數的可讀性
尾隨閉包是一個被書寫在函數調用括號外面(後面)的閉包表達式
上面的函數使用閉包調用:
如果閉包表達式是函數的唯一實參,而且使用了尾隨閉包的語法,那就不需要在函數名後邊寫圓括號
針對上面的函數,一般與閉包調用的對比:
示例 - 數組的排序
數組排序的底層代碼的定義:
由上面的底層代碼可知,想要自定義一個數組排序,只需要傳入一個有兩個參數,返回值是Bool的函數
自定義排序方法的多種調用方式(閉包和簡寫):
閉包(Closure)
網上有各種關於閉包的定義,個人覺得比較嚴謹的定義是 :
- 一個函數和它所捕獲的變量\常量環境組合起來,稱爲閉包
- 一般指定義在函數內部的函數
- 一般它捕獲的是外層函數的局部變量\常量
定義了一個閉包:
注:return plus前分配了一段堆空間,將num的值存儲到了這個堆空間,調用plus訪問的num實際上是堆空間的num, plus方法實際接收了兩個參數:i和堆空間地址
使用反彙編看它實現的底層彙編代碼:
由圖上紅色框標註的彙編代碼我們可以看出,在閉包裏調用了swift_allocObject方法,我們可以理解爲它在堆區申請了一塊內存空間,用來存儲它捕獲的外層函數的局部變量或常量也就是num,這也就決定了num不會立刻銷燬,由此我們可以看下它的計算結果:
func getFn() -> Fn {
var num = 0
func plus(_ i: Int) -> Int{
num += 1
return num
}
return plus
}
var fn = getFn()
print(fn(1)) //結果爲1
print(fn(2)) //結果爲3
print(fn(3)) //結果爲6
print(fn(4)) //結果爲10
可以看出計算結果是累加的,那就因爲num存儲在堆空間,沒有銷燬,上面的計算是對num的不斷累加。
注意:如果num是全局變量,則不會在堆空間開闢內存。
注意:每調用一次getFn()都會申請一個新的內存空間,舉例如下:
由上面的計算可以看出,fn1和fn2是分開獨立累加的,也就是fn1和fn2分別開闢了一塊新內存,互不影響
反彙編代碼如下:
由上面的反彙編代碼可以看出fn1和fn2前8個字節相同,因爲都是指向同一個plus方法,但是後8個字節不同,因爲分配的堆空間不同。
- 可以把閉包想象成是一個類的實例對象
- 內存在堆空間
- 捕獲的局部變量\常量就是對象的成員(存儲屬性)
- 組成閉包的函數就是類內部定義的方法
閉包舉例
- 第一個例子:
上面的閉包可以看成一個類:
- 第二個例子:
上面的閉包同樣可以看成一個類:
參數注意
如果返回值類型是函數類型,那麼參數的修飾要保持統一
自動閉包
- @autoclosure 會自動將 20 封裝成閉包 { 20 }
- @autoclosure 只支持 () -> T 格式的參數
- @autoclosure 並非只支持最後1個參數
- 空合併運算符 ?? 使用了 @autoclosure 技術
- 有@autoclosure、無@autoclosure,構成了函數重載
爲了避免與期望衝突,使用了@autoclosure的地方最好明確註釋清楚:這個值會被推遲執行。