本文主要介紹閉包表達式和閉包蟀拷,閉包表達式簡化了函數(shù)的調用问麸,閉包可以捕獲局部變量丹锹,在局部變量的作用域外也可以進行操作
主要內容:
- 閉包表達式
- 閉包使用
- 閉包原理
1泳姐、閉包表達式
1.1 閉包表達式認識
閉包表達式用來實現(xiàn)功能档泽,類似于函數(shù)的作用坦康,只是寫法不一樣
定義格式:
{
(參數(shù)列表) -> 返回值類型 in
函數(shù)體代碼
}
代碼:
/*
1攻泼、閉包表達式的寫法
*/
//1.1 函數(shù)
func sum(_ v1: Int, _ v2: Int) -> Int {
v1 + v2
}
print("sum\(sum(10, 20))")
//1.2 閉包表達式
var fn = {
(v1: Int,v2: Int) -> Int in
return v1 + v2
}
let result = fn(10,20)
//1.3 匿名閉包表達式
{
(v1: Int,v2: Int) -> Int in
return v1 + v2
}(10,20)
說明:
- 函數(shù)的功能使用閉包表達式也完全可以實現(xiàn)火架。
- 函數(shù)有的元素,閉包表達式也都有忙菠,只是書寫方式不一樣
- 注意in是用來分割函數(shù)類型和函數(shù)體的
- 閉包表達式需要使用{}來包含進去
- 如果需要賦給一個變量何鸡,就通過變量來調用,如果直接使用牛欢,就直接調用傳值
簡寫:
/*
2骡男、閉包表達式的簡寫
*/
func test2 (){
//2.1 函數(shù)
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
//2.2 閉包表達式正常格式
exec(v1: 10, v2: 20, fn: {
(v1: Int, v2: Int) -> Int in
return v1 + v2
})
//2.3 簡寫:參數(shù)類型
exec(v1: 10, v2: 20, fn: {
v1, v2 in return v1 + v2
})
//2.4 簡寫:return
exec(v1: 10, v2: 20, fn: {
v1, v2 in v1 + v2
})
//2.5 簡寫:參數(shù)
exec(v1: 10, v2: 20, fn: { $0 + $1 })
//2.6 簡寫:只寫操作符
exec(v1: 10, v2: 20, fn: + )
}
test2()
說明:
- 可以將參數(shù)類型省略掉,因為在聲明中已經明確了類型
- 可以將return省略掉傍睹,因為函數(shù)體只有一個表達式
- 可以將參數(shù)省略掉隔盛,此時使用
1來表示第一個、第二個參數(shù)(因為在聲明中已經明確了參數(shù)類型拾稳,所以此時不用創(chuàng)建參數(shù)吮炕,直接通過占位符使用)
- 甚至可以將
1省略掉,只寫一個運算符(因為可以通過聲明判斷出兩個數(shù)需要進行運算)
注意:
- 與函數(shù)不同的是閉包中不用寫參數(shù)標簽也可以在調用時不寫參數(shù)名稱
- 在簡寫中访得,也可以不寫參數(shù)名稱龙亲,而是通過
1來表示第一個陕凹、第二個參數(shù)。此時所有的內容都是函數(shù)體了鳄炉。
- 在簡寫中杜耙,還有一種極簡方式,就是只寫操作符拂盯,不寫參數(shù)佑女,這樣編譯器會認為是操作這兩個參數(shù)的。這種就是極簡形式了
- 如果不用參數(shù)谈竿,直接返回值珊豹,不能直接省略,而是應該用_來占位一下
1.2 尾隨閉包
如果將一個很長的閉包表達式作為函數(shù)的最后一個實參榕订,可讀性較差,此時可以使用尾隨閉包增加代碼的可讀性蜕便。
尾隨閉包就是將閉包表達式書寫在調用函數(shù)的括號外面
代碼:
func test3 () {
/*
3劫恒、尾隨閉包
*/
//3.1 函數(shù)定義
func exec1(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
})
//3.2 函數(shù)調用時尾隨閉包
exec(v1: 10, v2: 20) { $0 + $1 }
}
test3()
說明:
- 可以看fn閉包表達式作為exec函數(shù)的參數(shù),但是本身是放在了括號的后邊轿腺,這就是所謂的尾隨
- 需要注意的是两嘴,如果閉包表達式是函數(shù)的唯一實參,而且使用了尾隨閉包的語法族壳,就不需要在函數(shù)后面寫()
1.3 案例說明
以數(shù)組排序的實際案例來使用閉包表達式
代碼:
/*
4憔辫、數(shù)組排序實例
*/
func test4 () {
//func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
var nums = [11, 2, 18, 6, 5, 68, 45]
//4.1 sort排序函數(shù)中最后一個參數(shù)設置閉包表達式
nums.sort(by: {
(i1: Int, i2: Int) -> Bool in
return i1 < i2
})
//各種簡寫
nums.sort(by: { i1, i2 in return i1 < i2 })//正常
nums.sort(by: { i1, i2 in i1 < i2 })//沒有return
nums.sort(by: { $0 < $1 })//沒有參數(shù)
nums.sort(by: <)//只有運算符
nums.sort() { $0 < $1 }//尾隨閉包
nums.sort { $0 < $1 }//最后一個參數(shù)可以不用寫()
print("nums:\(nums)")//nums:[2, 5, 6, 11, 18, 45, 68]
}
test4()
說明:
- 閉包表達式實現(xiàn)了排序操作
- 可以看到將閉包表達式作為了參數(shù)來傳遞
- 這里也把前面所講的所有簡寫方式都寫了一遍
2、閉包
閉包就是可以捕獲變量/常量的函數(shù)或閉包表達式仿荆,它有兩個特點贰您,函數(shù)/閉包表達式,捕獲局部變量拢操。因此可以看到它和OC中的block基本上是一樣的了锦亦。具體關于block的認識可以看我的另一篇博客
,如果理解block令境,再看閉包就會覺得很簡單了
我們在實際理解閉包時杠园,可以將其看做一個類,捕獲的變量是成員變量舔庶,函數(shù)/閉包表達式是類的成員函數(shù)抛蚁。
2.1 閉包的使用
代碼:
/*
1、閉包認識
*/
func test5 () {
//定義一個函數(shù)類型
typealias Fn = (Int) -> Int
//返回一個函數(shù)
func getFn() -> Fn {
var num = 0
//plus函數(shù)會捕獲num變量
//plus函數(shù)和捕獲的num變量形成了閉包
func plus(_ i: Int) -> Int {
num += i
return num
}
return plus
}
var fn1 = getFn()
var fn2 = getFn()
fn1(1) // 1
fn2(2) // 2
fn1(3) // 4
fn2(4) // 6
}
test5()
說明:
- 可以看到此時的plus就是一個閉包惕橙,因為它本身是一個函數(shù)瞧甩,但是會捕獲外部的num變量
- 這里就和block一樣,num是捕獲到了堆中弥鹦,所以plus中的num和getFun()函數(shù)中的num已經不是一個地址了亲配。他們二者之間沒有關系
2.2 變量捕獲的匯編分析
閉包變量包含16個字節(jié),前8個字節(jié)是函數(shù)地址,后8個字節(jié)是變量堆空間地址
2.2.1 函數(shù)查看
代碼:
匯編分析
說明:
2.2.2 不捕獲變量的閉包
代碼:
匯編分析
說明:
2.2.3 捕獲變量的閉包
代碼:
匯編分析
說明:
2.2.4 捕獲變量的時機
代碼:
/*
3吼虎、捕獲變量的時機
*/
func test7() {
typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 11
func plus(_ i: Int) -> Int {
num += i
return num
}
//捕獲的是14犬钢,而非11
num = 14
return plus
}
var fn1 = getFn()
fn1(1)//15
}
說明:
- 可以看到只有在返回這個plus函數(shù)地址的時候才會去捕獲,所以并不是在定義函數(shù)的時候捕獲思灰,而是在創(chuàng)建函數(shù)變量的時候捕獲
- 其實這里就算沒有return,只要是創(chuàng)建了plus函數(shù)對象就會捕獲的
2.3 多變量多函數(shù)的閉包
代碼:
/*
4玷犹、多變量多函數(shù)的分析
*/
func test8() {
func getFns() -> (Fn, Fn) {
var num1 = 0
var num2 = 0
func plus(_ i: Int) -> (Int, Int) {
num1 += i
num2 += i << 1
return (num1,num2)
}
func minus(_ i: Int) -> (Int, Int) {
num1 -= i
num2 -= i << 1
return (num1,num2)
}
return (plus,minus)
}
let (p, m) = getFns()
p(6)//(6,12)
p(5)//(1,2)
p(4)//(5,10)
p(3)//(2,4)
}
說明:
- 多個函數(shù)捕獲同一個變量,他們捕獲后是同一份
- 多個變量分開alloc洒疚,是不同的堆空間
- 閉包我們可以將其看做一個類里歹颓。捕獲的變量是成員變量,函數(shù)是類的成員函數(shù)油湖。所以對于成員變量就是同一份
- 如果有多個變量巍扛,他們需要多次alloc,并不是放在同一個堆空間乏德。此時的fn變量的大小就應該更多了撤奸,如果是兩個變量,那么就是24個字節(jié)了
2.4 數(shù)組循環(huán)閉包分析
代碼:
/*
5喊括、數(shù)組循環(huán)閉包分析
*/
func test9() {
//定義一個數(shù)組胧瓜,元素是函數(shù)
var functions: [() -> Int] = []
for i in 1...3 {
functions.append{ i }
}
for f in functions {
print("\(f())")
}
}
代碼分析:
- 創(chuàng)建一個函數(shù)數(shù)組([() -> Int]就表示存放函數(shù)的數(shù)組)
- 給數(shù)組添加三個函數(shù),每個函數(shù)的函數(shù)體都是返回一個i
- {}是尾隨閉包郑什,append拼接函數(shù)的參數(shù)只有這一個閉包府喳,所以就把()省略掉了。這個閉包的函數(shù)體就是返回i
- 之后循環(huán)執(zhí)行數(shù)組中的函數(shù)
說明:
- 在for循環(huán)中的閉包蘑拯,每次都會捕獲變量
- 也就是此時會創(chuàng)建多個堆空間
2.5 自動閉包
如果我們傳入的參數(shù)是需要作為包钝满,但是寫成包的樣式可讀性會很差,就可以設置成自動閉包申窘,系統(tǒng)幫我們閉包
代碼:
/*
6舱沧、自動閉包
使用關鍵字@autoclosure修飾參數(shù)
*/
func test10() {
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
//下面三條語句等效
getFirstPositive(10, 20)//自動閉包
getFirstPositive(10, {20})
getFirstPositive(10){20}//尾隨閉包
}
test10()
說明:
- 這三者是等效的,也就是說如果我再參數(shù)中加上了@autoclosure偶洋,那么在傳入參數(shù)的時候就不需要傳入閉包熟吏,直接傳入函數(shù)體后,編譯器會幫我們自動閉包
- 如果我們傳入的參數(shù)是需要作為包玄窝,但是寫成包的樣式可讀性會很差牵寺,就可以設置成自動閉包,系統(tǒng)幫我們閉包
- 本來我們需要{20}傳入閉包恩脂,但是我們傳入20后系統(tǒng)會判斷需要自動閉包就會將這個20作為閉包的函數(shù)體進行處理
注意:
- @autoclosure設置后會自動閉包帽氓,傳入的參數(shù)就不能是閉包。二者會沖突
- @autoclosure只支持() -> T格式的參數(shù)(參數(shù)為空俩块,有返回值)
- @autoclosure并非只只支持最后一個參數(shù)
- 有@autoclosure和沒有@autoclosure構成了函數(shù)重載