閉包表達(dá)式(Closure Expression)
在Swift中诉植,可以通過func定義一個(gè)函數(shù)嵌莉,也可以通過閉包表達(dá)式定義一個(gè)函數(shù)
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
var fn = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
fn(10, 20)
{
(v1: Int, v2: Int) -> Int in
return v1 + v2
}(10, 20)
{
(參數(shù)列表) -> 返回值類型 in
函數(shù)體代碼
}
閉包表達(dá)式的縮寫
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
exec(v1: 10, v2: 20, fn: {
(v1: Int, v2: Int) -> Int in
return v1 + v2
})
exec(v1: 10, v2: 20, fn: {
v1, v2 in return v1 + v2
})
exec(v1: 10, v2: 20, fn: {
v1, v2 in v1 + v2
})
exec(v1: 10, v2: 20, fn: { $0 + $1 })
exec(v1: 10, v2: 20, fn: +)
尾隨閉包
如果將一個(gè)很長的閉包表達(dá)式作為函數(shù)的最后一個(gè)實(shí)參,使用尾隨閉包可以增強(qiáng)函數(shù)的可讀性
尾隨閉包是一個(gè)被書寫在函數(shù)調(diào)用括號(hào)外面(后面)的閉包表達(dá)式
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
exec(v1: 10, v2: 20) {
$0 + $1
}
- 如果閉包表達(dá)式是函數(shù)的唯一實(shí)參苟鸯,而且使用了尾隨閉包的語法,那就不需要在函數(shù)名后邊寫圓括號(hào)
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
exec(fn: { $0 + $1 })
exec() { $0 + $1 }
exec { $0 + $1 }
示例 - 數(shù)組的排序
func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
/// 返回true: i1排在i2前面
/// 返回false: i1排在i2后面
func cmp(i1: Int, i2: Int) -> Bool {
// 大的排在前面
return i1 > i2
}
var nums = [11, 2, 18, 6, 5, 68, 45]
nums.sort(by: cmp)
// [68, 45, 18, 11, 6, 5, 2]
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 })
nums.sort(by: { $0 < $1 })
nums.sort(by: <)
nums.sort() { $0 < $1 }
nums.sort { $0 < $1 }
// [2, 5, 6, 11, 18, 45, 68]
忽略參數(shù)
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
exec {_,_ in 10} // 10
閉包 (Closure)
-
網(wǎng)上有各種關(guān)于閉包的定義,個(gè)人覺得比較嚴(yán)謹(jǐn)?shù)亩x是
一個(gè)函數(shù)和它所捕獲的變量\常量環(huán)境組合起來纪隙,稱為閉包
一般指定義在函數(shù)內(nèi)部的函數(shù)
一般它捕獲的是外層函數(shù)的局部變量\常量
typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 0 // 局部變量
func plus(_ i: Int) -> Int {
num += i
return num
}
return plus
} // 返回的plus和num形成了閉包
var fn1 = getFn()
var fn2 = getFn()
fn1(1) // 1
fn2(2) // 2
fn1(3) // 4
fn2(4) // 6
fn1(5) // 9
fn2(6) // 12
// 2分堆空間,后面推導(dǎo)過程
思考:如果num是全局變量呢?
func getFn() -> Fn {
var num = 0
return {
num += $0
return num
}
}
可以把閉包想象成是一個(gè)類的實(shí)例對(duì)象
內(nèi)存在堆空間
捕獲的局部變量\常量就是對(duì)象的成員(存儲(chǔ)屬性)
組成閉包的函數(shù)就是類內(nèi)部定義的方法
class Closure {
var num = 0
func plus(_ i: Int) -> Int {
num += i
return num
}
}
var cs1 = Closure()
var cs2 = Closure()
cs1.plus(1) // 1
cs2.plus(2) // 2
cs1.plus(3) // 4
cs2.plus(4) // 6
cs1.plus(5) // 9
cs2.plus(6) // 12
推導(dǎo)過程:
var num = 0 // 全局變量
typealias Fn = (Int) -> Int
func getFn() -> Fn {
func plus(_ i: Int) -> Int {
num += i
return num
}
return plus
} // 返回的plus和num形成了閉包
var fn = getFn()
print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))
/*
1
3
6
10
*/
如果是全局變量的話扛或,訪問的都是同一塊內(nèi)存绵咱。輸出值如上
換成局部變量
- 結(jié)果和全局變量一樣,為什么局部變量沒有銷毀呢熙兔?
typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 0 // 局部變量
func plus(_ i: Int) -> Int {
// num += i
//return num
return i
}
return plus
}
var fn = getFn()
- 先查看一下上面這份代碼的匯編
leaq 0xd(%rip), %rax // rip+0xd 算出來的地址悲伶,直接給rax 找房間號(hào)
movq 0xd(%rip), %rax // rip+0xd 算出來的地址所在的存儲(chǔ)空間,取出8個(gè)字節(jié)住涉,賦值給rax 去房間號(hào)里找人
- 接下來看下用到局部變量后的匯編代碼
typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 0 // 局部變量
func plus(_ i: Int) -> Int {
num += i
return num
}
return plus
}
var fn = getFn()
print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))
/*
1
3
6
10
*/
推斷堆空間存儲(chǔ)了num麸锉,保活了num舆声,訪問的同一個(gè)內(nèi)存空間
匯編 movq $0, 0x8(%rax)
TODO 匯編分析閉包本質(zhì)花沉,7-1
練習(xí) 1
typealias Fn = (Int) -> (Int, Int)
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(5) // (5, 10)
m(4) // (1, 2)
p(3) // (4, 8)
m(2) // (2, 4)
class Closure {
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)
}
}
// plus和minus共享一份
var cs = Closure()
cs.plus(5) // (5, 10)
cs.minus(4) // (1, 2)
cs.plus(3) // (4, 8)
cs.minus(2) // (2, 4)
練習(xí) 2
var functions: [() -> Int] = []
for i in 1...3 {
functions.append { i }
}
for f in functions {
print(f())
}
// 1
// 2
// 3
class Closure {
var i: Int
init(_ i: Int) {
self.i = i
}
func get() -> Int {
return i
}
}
var clses: [Closure] = []
for i in 1...3 {
clses.append(Closure(i))
}
for cls in clses {
print(cls.get())
}
注意
- 如果返回值是函數(shù)類型,那么參數(shù)的修飾要保持統(tǒng)一
func add(_ num: Int) -> (inout Int) -> Void {
func plus(v: inout Int) {
v += num
}
return plus
}
var num = 5
add(20)(&num)
print(num)
自動(dòng)閉包
@autoclosure 會(huì)自動(dòng)將 20 封裝成閉包 { 20 }
@autoclosure 只支持 () -> T 格式的參數(shù)
@autoclosure 并非只支持最后1個(gè)參數(shù)
空合并運(yùn)算符 ?? 使用了 @autoclosure 技術(shù)
有@autoclosure媳握、無@autoclosure碱屁,構(gòu)成了函數(shù)重載
// 如果第1個(gè)數(shù)大于0,返回第一個(gè)數(shù)蛾找。否則返回第2個(gè)數(shù)
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirstPositive(10, 20) // 10
getFirstPositive(-2, 20) // 20
getFirstPositive(0, -4) // -4
// 改成函數(shù)類型的參數(shù)娩脾,可以讓v2延遲加載
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int? {
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4) { 20 }
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20)
- 為了避免與期望沖突,使用了@autoclosure的地方最好明確注釋清楚:這個(gè)值會(huì)被推遲執(zhí)行