閉包是可以在你的代碼中被傳遞和引用的功能性獨(dú)立代碼塊鹉梨。閉包在實(shí)現(xiàn)上是一個結(jié)構(gòu)體常柄,它存儲了一個函數(shù)(通常是其入口地址)和一個關(guān)聯(lián)的環(huán)境(相當(dāng)于一個符號查找表)巷疼。Swift 中的閉包和 C 以及 Objective-C 中的 blocks 很像,還有其他語言中的匿名函數(shù)也類似珊膜。
閉包能夠捕獲和存儲定義在其上下文中的任何常量和變量的引用容握,這也就是所謂的閉合并包裹那些常量和變量,因此被稱為“閉包”车柠,Swift 能夠?yàn)槟闾幚硭嘘P(guān)于捕獲的內(nèi)存管理的操作剔氏。
一、閉包定義
閉包符合如下三種形式中的一種:
- 全局函數(shù)是一個有名字但不會捕獲任何值的閉包竹祷;
- 內(nèi)嵌函數(shù)是一個有名字且能從其上層函數(shù)捕獲值的閉包谈跛;
- 閉包表達(dá)式是一個輕量級語法所寫的可以捕獲其上下文中常量或變量值的沒有名字的閉包。
簡單點(diǎn)說:閉包是一個捕獲了上下文的常量或者變量的函數(shù)塑陵。
全局函數(shù)例子
func test() {
print("test func")
}
test
函數(shù)是一個全局函數(shù)感憾,是特殊閉包,這里沒有捕獲值令花。
內(nèi)嵌函數(shù)例子
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
makeIncrementer
是一個官方的例子阻桅,incrementer
稱為內(nèi)嵌函數(shù),同時從上層函數(shù)makeIncrementer
中捕獲變量runningTotal
兼都。
閉包表達(dá)式例子
比如:
{ (age: Int) in
return age
}
這個就是我們熟知的閉包表達(dá)式嫂沉,是一個匿名函數(shù),而且從上下文中捕獲變量和常量扮碧。
Swift
的閉包表達(dá)式擁有簡潔的風(fēng)格趟章,鼓勵在常見場景中實(shí)現(xiàn)簡潔,無累贅的語法芬萍。常見的優(yōu)化包括:
- 利用上下文推斷形式參數(shù)和返回值的類型尤揣;
- 單表達(dá)式的閉包可以隱式返回(省略
return
關(guān)鍵字); - 簡寫實(shí)際參數(shù)名(比如 $0)柬祠;
- 尾隨閉包語法北戏。
二、閉包表達(dá)式
閉包表達(dá)式一般定義:
{ (parameters) -> (return type) in
statements
}
OC
中的Block
其實(shí)是一個匿名函數(shù)漫蛔,同理閉包表達(dá)式要具備:
- 作用域(也就是
{}
); - 參數(shù)和返回值;
- 函數(shù)體(
in
之后的代碼);
閉包作為變量:
var closure : (Int) -> Int = { (age: Int) in
return age
}
閉包聲明為可選類型(需要表達(dá)式整體可選):
閉包聲明為常量(賦值后不能改變):
閉包作為函數(shù)參數(shù):
func test(param: () -> Int) {
print(param())
}
var age = 18
test {() -> Int in
age += 10
return age
}
三嗜愈、尾隨閉包
當(dāng)我們把閉包表達(dá)式作為函數(shù)的最后一個參數(shù)旧蛾,如果閉包表達(dá)式很長,可以通過尾隨閉包的書寫方式提高代碼的可讀性蠕嫁。尾隨閉包是一個被書寫在函數(shù)形式參數(shù)的括號外面(后面)的閉包表達(dá)式锨天,但它仍然是這個函數(shù)的實(shí)際參數(shù)。當(dāng)你使用尾隨閉包表達(dá)式時剃毒,不需要把第一個尾隨閉包寫對應(yīng)的實(shí)際參數(shù)標(biāo)簽病袄。函數(shù)調(diào)用可包含多個尾隨閉包。
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ num1: Int, _ num2 : Int, _ num3: Int) -> Bool) -> Bool {
return by(a, b, c)
}
上面的test
函數(shù)不使用尾隨閉包赘阀,作為()
中參數(shù)常規(guī)調(diào)用的情況下如下:
test(10, 20, 30, by: { (num1: Int, num2: Int, num3: Int) -> Bool in
return (num1 + num2 < num3)
})
在這里我們找方法的調(diào)用比較困難益缠,隨著閉包參數(shù)增加以及嵌套會越來越難找。
我們在調(diào)用的時候編譯器會默認(rèn)格式化為尾隨閉包:
test(10, 20, 30) { (num1, num2, num3) -> Bool in
return (num1 + num2 < num3)
}
這里我們一看就知道是一個函數(shù)調(diào)用基公,后面是一個閉包表達(dá)式幅慌。{}
放在了函數(shù)體外,增加了可讀性轰豆。
假如我們要讀一個數(shù)組排序sort
:
var array = [3,1,2]
//普通調(diào)用方式
array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })
//閉包作為最后一個參數(shù)胰伍,有且僅有閉包一個參數(shù),可以優(yōu)化為尾隨閉包并且省略小括號
array.sort{(item1 : Int, item2: Int) -> Bool in return item1 < item2 }
//省略參數(shù)類型酸休,根據(jù)array推斷類型
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
//return 是 比較表達(dá)式骂租,返回值Bool省略
array.sort(by: {(item1, item2) in return item1 < item2 })
//單表達(dá)式省略return關(guān)鍵字
array.sort{(item1, item2) in item1 < item2 }
//簡寫實(shí)際參數(shù)
array.sort{ return $0 < $1 }
//省略return關(guān)鍵字
array.sort{ $0 < $1 }
//只傳一個 < 默認(rèn)比較 $0 和 $1,這里必須寫by雨席,by不知道傳的是什么了菩咨。
array.sort(by: <)
//什么都不傳,默認(rèn) <
array.sort()
個人習(xí)慣使用:
array.sort{ $0 < $1 }
四陡厘、捕獲值
繼續(xù)官方makeIncrementer
例子抽米,這里便于分析省略了參數(shù)。
func makeIncrementer() -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
調(diào)用:
print(makeInc())
print(makeInc())
print(makeInc())
//輸出
1
2
3
換一下調(diào)用方式:
print(makeIncrementer()())
print(makeIncrementer()())
print(makeIncrementer()())
//輸出
1
1
1
第一種方式的輸出結(jié)果也就意味者runningTotal
不僅僅是一個局部變量了糙置。分析下SIL
:
-
alloc_box
分配了一個變量給到了runningTotal
云茸。runningTotal
存儲到了堆上。 - 閉包調(diào)用前進(jìn)行了retain谤饭,調(diào)用后進(jìn)行了release标捺。
斷點(diǎn)驗(yàn)證:
閉包捕獲值的本質(zhì)是在堆空間上開辟內(nèi)存空間,將變量放到堆中揉抵。函數(shù)以及捕獲的變量/常量構(gòu)成閉包
總結(jié):
- 一個閉包能夠偶從上下文捕獲已被定義的常量和變量亡容。即使定義這些常量和變量的原作用域已經(jīng)不存在了,閉包仍能夠在其函數(shù)體內(nèi)引用和修改這些值冤今;
- 每次修改捕獲值的時候闺兢,修改的都是堆區(qū)中的
value
; - 每次重新執(zhí)行當(dāng)前函數(shù)的時候,都會重新創(chuàng)建內(nèi)存空間戏罢。
五屋谭、閉包是引用類型
在上面的例子中脚囊,將函數(shù)賦值給變量,那么這個時候變量中存儲的是什么桐磁? 通過lldb
打印不能很好地看出來存儲的是什么悔耘, 從SIL
中看不出返回值是什么。這個時候可以通過IR
觀察下數(shù)據(jù)的構(gòu)成我擂。
IR語法
數(shù)組
// 元素?cái)?shù)量 數(shù)據(jù)類型
[<elementnumber> x <elementtype>]
//example衬以,iN:多少位的整形
alloca [24 x i8], align 8 24個i8都是0,24字節(jié)
結(jié)構(gòu)體
//結(jié)構(gòu)體名稱 結(jié)構(gòu)體成員列表
%T = type {<type list>} // 這種和C語言結(jié)構(gòu)體類似
//example
%swift.refcounted = type { %swift.type*, i64 }
指針
<type> *
//example
i64* //64位整形
getelementptr指令
LLVM中獲取數(shù)組和結(jié)構(gòu)體的成員校摩,通過getelementptr
泄鹏,語法規(guī)則如下:
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
看一個LLVM
官網(wǎng)當(dāng)中的例子:
struct munger_struct {
int f1;
int f2;
};
void munge(struct munger_struct *P) {
P[0].f1 = P[1].f1 + P[2].f2;
}
struct munger_struct array[3];
int main(int argc, const char * argv[]) {
// insert code here...
munge(array);
return 0;
}
為了方便閱讀,注釋掉main
生成IR
文件:
rm -rf ${SRCROOT}/main.ll
clang ${SRCROOT}/IRTest/main.c -emit-llvm -S -c >> ./main.ll && open main.ll
IR
代碼:
;結(jié)構(gòu)體
%struct.munger_struct = type { i32, i32 }
;array 數(shù)量3 結(jié)構(gòu)體類型
@array = common global [3 x %struct.munger_struct] zeroinitializer, align 16
; Function Attrs: noinline nounwind optnone ssp uwtable
define void @munge(%struct.munger_struct* %0) #0 {
;創(chuàng)建內(nèi)存空間秧耗,存放結(jié)構(gòu)體首地址。%2是一個二級指針
%2 = alloca %struct.munger_struct*, align 8
store %struct.munger_struct* %0, %struct.munger_struct** %2, align 8
%3 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
;第一個結(jié)構(gòu)體
%4 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
;第一個結(jié)構(gòu)體第一個元素
%5 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %4, i32 0, i32 0
%6 = load i32, i32* %5, align 4
%7 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
;第二個結(jié)構(gòu)體
%8 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %7, i64 2
;第二個結(jié)構(gòu)體第二個元素
%9 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %8, i32 0, i32 1
%10 = load i32, i32* %9, align 4
%11 = add nsw i32 %6, %10
;取到結(jié)構(gòu)體基地址舶治,數(shù)組的首地址
%12 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
;index = 0分井,基地址偏移0。0 *結(jié)構(gòu)體大小霉猛。拿到結(jié)構(gòu)體的第0個地址尺锚。
%13 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %12, i64 0
;i32 0 相對于自己偏移0。拿到結(jié)構(gòu)體首地址惜浅,第二個i32 0 拿到結(jié)構(gòu)體第一個元素
%14 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %13, i32 0, i32 0
store i32 %11, i32* %14, align 4
ret void
}
int array[4] = {1, 2, 3, 4};
int a = array[0];
這里的int a = array[0]
對應(yīng)到LLVM
的代碼如下:
a = getelementptr inbounds [4 x i32], [4 x i32]* array, i64 0, i64 0
也就是下面這樣的圖:
第一個指針使用基本數(shù)據(jù)類型[4 * i32]
瘫辩,返回的指針前進(jìn)0*16
字節(jié),也就是數(shù)組首地址坛悉。第二個index伐厌,返回基本類型i32
前進(jìn)0
字節(jié),就是當(dāng)前數(shù)組第一個元素裸影。返回的指針類型為i32*
挣轨。
- 第一個索引(
%struct.munger_struct* %13, i32 0
)不改變返回的指針類型(%14 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %13, i32 0, i32 0
,%14最終存儲i32
)轩猩。ptrval
(ptrval
這里對應(yīng)%13
)前面的*
對應(yīng)什么類型(%struct.munger_struct
)就返回什么類型卷扮。 - 第一個索引的偏移量由第一個索引的值(
i32 0
)和第一個ty
(%struct.munger_struct* %13
)指定的基本類型共同確定。也就是%struct.munger_struct* %13, i32 0
均践。 - 后面的索引都是在數(shù)組/結(jié)構(gòu)體內(nèi)進(jìn)行索引晤锹。
- 每增加一個索引,就會使得該索引使用的基本類型和返回的指針類型去掉一層彤委。(也就是上面的例子鞭铆,第一個索引是
[4 * i32]
去掉一層是i32
)。
GEP
作用于結(jié)構(gòu)體時葫慎,索引一定要是常量衔彻。該指令只是返回一個偏移后的指針薇宠,并沒有訪問內(nèi)存。
閉包IR分析
回到makeIncrementer
的例子艰额,分析下makeIncrementer
的IR
:
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
%3 = call swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer() -> () -> Swift.Int"()
%4 = extractvalue { i8*, %swift.refcounted* } %3, 0
%5 = extractvalue { i8*, %swift.refcounted* } %3, 1
store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"main.makeInc : () -> Swift.Int", i32 0, i32 0), align 8
store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"main.makeInc : () -> Swift.Int", i32 0, i32 1), align 8
ret i32 0
}
define hidden swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer() -> () -> Swift.Int"() #0 {
entry:
%runningTotal.debug = alloca %TSi*, align 8
%0 = bitcast %TSi** %runningTotal.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
;%1是swift_allocObject分配的內(nèi)存空間
%1 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #1
;8字節(jié)存值澄港,按位轉(zhuǎn)換創(chuàng)建出來的HeapObject
%2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
%3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
%4 = bitcast [8 x i8]* %3 to %TSi*
store %TSi* %4, %TSi** %runningTotal.debug, align 8
;_value是%TSi類型的地址空間
%._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
;把0存儲到 _value,也就是[8 x i8]連續(xù)的地址空間中柄沮。
store i64 0, i64* %._value, align 8
%5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
call void @swift_release(%swift.refcounted* %1) #1
;往結(jié)構(gòu)體中插入值回梧。
;`bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int" to i8*)`
;bitcast 按位轉(zhuǎn)換 把內(nèi)嵌函數(shù)incrementer為void*存到`i8*`內(nèi)存空間中。
;之后插入 %swift.refcounted* %1
%6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
;返回一個結(jié)構(gòu)體`%6`祖搓,結(jié)構(gòu)體包含一個 `i8* `和 `%swift.refcounted*`
ret { i8*, %swift.refcounted* } %6
}
在
IR
中iN
表示整數(shù)類型狱意,i1
表示布爾類型~
,i8*
代表void*
拯欧。
bitcase
在進(jìn)行指向很轉(zhuǎn)換的過程中sourceType
的位大小和destType
必須相同详囤。如果sourceType
為指針,則destType
也必須是相同大小的指針镐作,轉(zhuǎn)換就好像是該值已存儲到內(nèi)存中并作為destType
類型讀取一樣藏姐。類似于Swift
中unsafeBitCast
。
%swift.refcounted*
定義如下:
%swift.function = type { i8*, %swift.refcounted* }
;結(jié)構(gòu)體该贾,結(jié)構(gòu)體包含{結(jié)構(gòu)體羔杨,i64位整形}
%swift.refcounted = type { %swift.type*, i64 }; = {i64,i64} //heapObject
;結(jié)構(gòu)體存放i64位整形
%swift.type = type { i64 }
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%TSi = type <{ i64 }>
-
makeIncrementer
返回返回一個結(jié)構(gòu)體%6
,結(jié)構(gòu)體包含一個i8*
和%swift.refcounted*
杨蛋。這里的i8*
可以理解為void*
兜材,swift.refcounted*
是一個結(jié)構(gòu)體指針。 -
%swift.refcounted*
是一個heapObject
還原下makeIncrementer
返回值如下:
struct FunctionData {
var ptr: UnsafeRawPointer
var unKnown: UnsafeRawPointer
}
由于%swift.refcounted*
是一個heapObject
,還原后如下:
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refcount2: UInt32
}
FunctionData
結(jié)構(gòu)體中的unKnown
就是heapObject
在index
為1
的地方插入了_value
(就是捕獲的值)逞力,所以unKnown
的結(jié)構(gòu)體如下:
struct Box<T> {
var refCounted:HeapObject
var value: T
}
//function就變成了
struct FunctionData<BoxType> {
var ptr: UnsafeRawPointer
var captureValue: UnsafePointer<BoxType>
}
嘗試綁定觀察下:
改變下參數(shù)類型試試(
() -> Int
):
//改變下類型
let makeInc = makeIncrementer()
let ptr = UnsafeMutablePointer<() -> Int>.allocate(capacity: 1)
ptr.initialize(to: makeInc)
let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1){ $0.pointee }
print(funcationData.ptr)
print(funcationData.captureValue)
查看下funcationData.ptr
的地址
(lldb) cat address 0x00000001000029c0
&0x00000001000029c0, partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed () -> (@unowned Swift.Int) to @escaping @callee_guaranteed () -> (@out Swift.Int) <+0> , ($sSiIegd_SiIegr_TRTA)SwiftClouse.__TEXT.__text
恢復(fù)下這個函數(shù)的符號,看到綁定的并不是內(nèi)嵌函數(shù):
所以最終對makeInc
包裝一層:
struct VoidIntFunc {
var f:() -> Int
}
完整代碼如下:
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refcount2: UInt32
}
struct Box<T> {
var refCounted:HeapObject
var value: T //捕獲值
}
struct FunctionData<BoxType> {
var ptr: UnsafeRawPointer //內(nèi)嵌函數(shù)地址
var captureValue: UnsafePointer<BoxType> //捕獲值地址
}
// 這里包裝為了返回值不受影響曙寡。
struct VoidIntFunc {
var f:() -> Int
}
func makeIncrementer() -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = VoidIntFunc(f: makeIncrementer())
//拿到包裝的結(jié)構(gòu)體地址
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
//ptr指向f
ptr.initialize(to: makeInc)
//把f綁定到FunctionData結(jié)構(gòu)體
let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1){
$0.pointee
}
//funcationData中第一個值ptr,也就是內(nèi)嵌函數(shù)地址
print(funcationData.ptr)
//funcationData中第二個指針指向的值掏击。也就是捕獲值
print(funcationData.captureValue.pointee)
輸出
0x0000000100002bd0
Box<Int>(refCounted: SwiftClouse.HeapObject(type: 0x0000000100004038, refCount1: 3, refcount2: 2), value: 0)
在終端搜索下0x0000000100002bd0
對應(yīng)的符號表:
nm -p /Users/**/Library/Developer/Xcode/DerivedData/SwiftClouse-fxzfztmmgwufmzgqbbajkncgvxrb/Build/Products/Debug/SwiftClouse | grep 0000000100002bd0
輸出
0000000100002bd0 t _$s11SwiftClouse15makeIncrementerSiycyF11incrementerL_SiyFTA
可以看到0x0000000100002bd0
就是內(nèi)嵌函數(shù)的地址卵皂。
那么如果是捕獲多個值呢?
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
IR
代碼:
返回值相同:
ret { i8*, %swift.refcounted* } %12
%12
:
%12 = insertvalue { i8*, %swift.refcounted* } {
;強(qiáng)轉(zhuǎn)內(nèi)嵌函數(shù)地址放入 i8* 中
i8* bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer(forIncrement: Swift.Int) -> () -> Swift.Int"
to i8*), %swift.refcounted* undef },
;%8 插入 index1 也就是 %swift.refcounted*
%swift.refcounted* %8, 1
%8
:
%8 = call noalias %swift.refcounted* @swift_allocObject(%swift.type*
;類型是 full_boxmetadata砚亭。
getelementptr inbounds (%swift.full_boxmetadata,
;i32 0 相對于 %swift.full_boxmetadata 的偏移0灯变。
%swift.full_boxmetadata* @metadata.3, i32 0,
; %swift.full_boxmetadata 地址 index2
i32 2),
;分配32字節(jié)
i64 32,
;8字節(jié)對齊
i64 7) #2
%swift.full_boxmetadata
:
; index 2 取的是 %swift.type
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%swift.refcounted = type { %swift.type*, i64 }
;i64
%swift.type = type { i64 }
完整解讀:
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
ret i32 0
}
define hidden swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer(forIncrement: Swift.Int) -> () -> Swift.Int"(i64 %0) #0 {
entry:
%amount.debug = alloca i64, align 8
%1 = bitcast i64* %amount.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
%runningTotal.debug = alloca %TSi*, align 8
%2 = bitcast %TSi** %runningTotal.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
;存儲 amount 到 %amount.debug
store i64 %0, i64* %amount.debug, align 8
;開辟空間
%3 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #2
%4 = bitcast %swift.refcounted* %3 to <{ %swift.refcounted, [8 x i8] }>*
%5 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %4, i32 0, i32 1
%6 = bitcast [8 x i8]* %5 to %TSi*
store %TSi* %6, %TSi** %runningTotal.debug, align 8
;獲日纸伞(%swift.refcounted* index0)地址給到_value
%._value = getelementptr inbounds %TSi, %TSi* %6, i32 0, i32 0
;一卒蘸、10存入_value(%swift.refcounted* index0)也就是runningTotal
store i64 10, i64* %._value, align 8
%7 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %3) #2
;開辟空間鬓照,取swift Type 地址
%8 = call noalias %swift.refcounted* @swift_allocObject(%swift.type*
;類型是 full_boxmetadata匾浪。
getelementptr inbounds (%swift.full_boxmetadata,
;i32 0 相對于 %swift.full_boxmetadata 的偏移0挟冠。
%swift.full_boxmetadata* @metadata.3, i32 0,
; %swift.full_boxmetadata 地址 index2
i32 2),
;分配32字節(jié)
i64 32,
;8字節(jié)對齊
i64 7) #2
%9 = bitcast %swift.refcounted* %8 to <{ %swift.refcounted, %swift.refcounted*, %TSi }>*
;index1也就是新開辟的(%swift.refcounted*)
%10 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 1
;%3存儲到%10噩咪,新開辟的空間也就是%8弄慰。%3就是runningTotal 這里是**地址的地址勺远。所以runningTotal獲取要通過地址的地址。
;二耙替、runningTotal存入到新開辟的 %swift.refcounted** index 1
store %swift.refcounted* %3, %swift.refcounted** %10, align 8
;index2(%swift.refcounted)
%11 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 2
;取地址 index2的 %swift.refcounted 的index0
%._value1 = getelementptr inbounds %TSi, %TSi* %11, i32 0, i32 0
;三亚侠、把a(bǔ)mcount存儲到_value1(新開辟的%8的index2的 %swift.refcounted 的index0),這里也就是runningTotal(**)在amcount(*)前
store i64 %0, i64* %._value1, align 8
call void @swift_release(%swift.refcounted* %3) #2
;四俗扇、index1 插入 %8 結(jié)構(gòu)體(runningTotal(**)在amcount(*))
%12 = insertvalue { i8*, %swift.refcounted* } {
;強(qiáng)轉(zhuǎn)內(nèi)嵌函數(shù)地址放入 i8* 中
i8* bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer(forIncrement: Swift.Int) -> () -> Swift.Int"
to i8*), %swift.refcounted* undef },
;%8(runningTotal + amount) 插入 index1 也就是 %swift.refcounted*
%swift.refcounted* %8, 1
;返回值沒有變
ret { i8*, %swift.refcounted* } %12
}
- 先存儲
runningTotal(**)
再存儲amcount(*)
- 最終一起插入結(jié)構(gòu)體
%swift.refcounted*
中包裝返回{ i8*, %swift.refcounted* }
再看下ptr
和captureValue
驗(yàn)證下:
let makeInc = VoidIntFunc(f: makeIncrementer(forIncrement: 20))
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
ptr.initialize(to: makeInc)
let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1){
$0.pointee
}
print(funcationData.ptr)
print(funcationData.captureValue)
再還原下結(jié)構(gòu)體
box
:
struct Box<T1,T2> {
var refCounted:HeapObject
var valueBox: UnsafePointer<T1>
var value: T2 //捕獲
}
調(diào)用修改:
let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int,Int>>.self, capacity: 1){
$0.pointee
}
驗(yàn)證:
多捕獲幾個值看看:
func makeIncrementer(_ amount: Int, _ amount1: Int) -> () -> Int {
var runningTotal = 1
var runningTota1 = 2
func incrementer() -> Int {
runningTotal += amount
runningTota1 += amount1
return runningTotal
}
return incrementer
}
let makeInc = VoidIntFunc(f: makeIncrementer(10,11))
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
ptr.initialize(to: makeInc)
let funcationData = ptr.withMemoryRebound(to: FunctionData<Box<Int,Int>>.self, capacity: 1){
$0.pointee
}
print(funcationData.ptr)
print(funcationData.captureValue)
- 按捕獲的順序存儲硝烂,局部變量是指針的指針,參數(shù)是指針铜幽。
總結(jié):
- 捕獲值原理的本質(zhì)是在堆區(qū)開辟一塊空間滞谢,把當(dāng)前捕獲的值放在開辟的空間上。相當(dāng)于把值用
box
包裝了一層除抛。 - 修改捕獲值的時候去堆上把變量拿出來
- 閉包是引用類型(地址傳遞)狮杨,閉包的底層結(jié)構(gòu)是結(jié)構(gòu)體:
FuncationData
存儲了函數(shù)地址和捕獲值地址
函數(shù)也是引用類型
func makeIncrementer(amount: Int) -> Int {
let runningTotal = 10
return runningTotal + amount
}
var makeInc = makeIncrementer
對應(yīng)的IR
:
;i8*和 %swift.refcounted*
%swift.function = type { i8*, %swift.refcounted* }
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
;函數(shù)地址轉(zhuǎn)換為void* 放到 %swift.function 結(jié)構(gòu)體第一個元素 index0
store i8* bitcast (i64 (i64)* @"main.makeIncrementer(amount: Swift.Int) -> Swift.Int" to i8*), i8** getelementptr inbounds (%swift.function, %swift.function* @"main.makeInc : (Swift.Int) -> Swift.Int", i32 0, i32 0), align 8
;index1 存 null
store %swift.refcounted* null, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"main.makeInc : (Swift.Int) -> Swift.Int", i32 0, i32 1), align 8
代碼驗(yàn)證:
struct FunctionData {
var ptr: UnsafeRawPointer //內(nèi)嵌函數(shù)地址
var captureValue: UnsafeRawPointer? //捕獲值地址
}
// 這里包裝為了返回值不受影響
struct VoidIntFunc {
var f:(Int) -> Int
}
func makeIncrementer(amount: Int) -> Int {
let runningTotal = 10
return runningTotal + amount
}
let makeInc = VoidIntFunc(f: makeIncrementer)
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
ptr.initialize(to: makeInc)
let funcationData = ptr.withMemoryRebound(to: FunctionData.self, capacity: 1){
$0.pointee
}
print(funcationData.ptr)
print(funcationData.captureValue)
函數(shù)的本質(zhì)傳遞的是結(jié)構(gòu)體{函數(shù)地址,null
}到忽,結(jié)構(gòu)體只有指針橄教,捕獲值為null
。所以函數(shù)是引用類型喘漏。
六颤陶、逃逸閉包
當(dāng)閉包作為一個實(shí)際參數(shù)傳遞給一個函數(shù)的時候,并且是在函數(shù)返回之后調(diào)用陷遮,我們就說這個閉包逃逸了。當(dāng)我們聲明一個接受閉包作為形式參數(shù)的函數(shù)時垦江,在形式參數(shù)前加@escaping
來明確閉包是允許逃逸的帽馋。Swift 3.0
之后,系統(tǒng)默認(rèn)閉包參數(shù)是@noescaping
比吭。
非逃逸閉包
func makeIncrementer(amount: Int, handler: (Int) -> Void) {
handler(amount)
}
對應(yīng)的SIL
:
非逃逸閉包(
@noescaping
)1.函數(shù)體內(nèi)執(zhí)行;
2.函數(shù)執(zhí)行完之后绽族,閉包表達(dá)式消失。
- 非逃逸閉包不會產(chǎn)生循環(huán)引用衩藤;
- 非逃逸閉包編譯器會做優(yōu)化吧慢,省略掉一些內(nèi)存管理調(diào)用;
- 非逃逸閉包上下文會優(yōu)化保存在棧上而不是堆上赏表;(這個沒有驗(yàn)證出來)
逃逸閉包
存儲調(diào)用
class HotpotCat {
var complitionHandler: ((Int) -> Void)?
func makeIncrementer(amount: Int, handler: @escaping (Int) -> Void) {
var runningTotal = 0
runningTotal += amount
self.complitionHandler = handler
}
func doSomething() {
self.makeIncrementer(amount: 10) {
print($0)
}
}
deinit {
print("LGTeaher deinit")
}
}
var hp = HotpotCat()
hp.doSomething()
hp.complitionHandler?(10)
10
這里hp
并沒有釋放检诗。
class HotpotCat {
func makeIncrementer(amount: Int, handler: @escaping (Int) -> Void) {
var runningTotal = 0
runningTotal += amount
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
handler(runningTotal)
}
}
func doSomething() {
self.makeIncrementer(amount: 10) {
print($0)
}
}
deinit {
print("LGTeaher deinit")
}
}
//didfinishlaunch中調(diào)用(runloop)
var hp = HotpotCat()
hp.doSomething()
10
LGTeaher deinit
這里hp
釋放了间狂。
逃逸閉包(@escaping
)
1.閉包作為參數(shù)傳遞給函數(shù)何鸡;
2.函數(shù)返回之后調(diào)用氢惋。(延遲調(diào)用/存儲骚亿,后續(xù)調(diào)用)
由于逃逸閉包很耗資源俱笛,除了上面兩種情況盡量不要聲明為逃逸閉包浆兰。逃逸閉包顯示引用self
提示有可能造成循環(huán)引用,自己注意解決轿腺。
七、自動閉包
自動閉包是一種自動創(chuàng)建的用來把作為實(shí)際參數(shù)傳遞給函數(shù)的表達(dá)式打包的閉包喧务。它不接受任何實(shí)際參數(shù)孽亲,并且當(dāng)它被調(diào)用時玲昧,它會返回內(nèi)部打包的表達(dá)式的值亲配。好處在于通過寫普通表達(dá)式代替顯式閉包而使你省略包圍函數(shù)形式參數(shù)的括號思灰。
濫用自動閉包會導(dǎo)致你的代碼難以讀懂。上下文和函數(shù)名應(yīng)該寫清楚求值是延遲的肺魁。如果你想要自動閉包允許逃逸怎诫,就同時使用 @autoclosure 和 @escaping 標(biāo)志舱沧。
func myDebugPrint(_ condition: Bool , _ message: String){
if condition {
print("debug:\(message)")
}
}
myDebugPrint(true, "Application Error Occured")
上面例子比如條件控制打印一些log信息。條件為true
的時候打印玄窝。message是一個字符串牵寺,那么如果message是別的功能模塊生成的呢?
func myDebugPrint(_ condition: Bool , _ message: String){
if condition {
print("debug:\(message)")
}
}
func test() -> String {
return "Application Error Occured"
}
myDebugPrint(true, test())
這個時候如果條件為false
恩脂,test
函數(shù)依然會調(diào)用帽氓,本質(zhì)上就造成了浪費(fèi)。
把message
改成閉包在條件為true
的時候執(zhí)行閉包
func myDebugPrint(_ condition: Bool , _ message: () -> String){
if condition {
print(message())
}
}
func test() -> String {
return "Application Error Occured"
}
myDebugPrint(true, test)
這個時候基本完美了东亦,但如果這塊模塊是一個公用模塊杏节,是不是應(yīng)該兼容message
為String
的情況?
這個時候自動閉包就派上用場了
func myDebugPrint(_ condition: Bool , _ message: @autoclosure () -> String){
if condition {
print(message())
}
}
func test() -> String {
return "Application Error Occured"
}
myDebugPrint(false, test())
myDebugPrint(true, "Network Error")
- 這里
test()
要帶()
和普通閉包不同典阵,只會在condition
為true
的情況下調(diào)用test()
奋渔。 -
myDebugPrint(true, "Network Error")
參數(shù)相當(dāng)于用閉包包裹字符串直接return
:
{
return "Network Error"
}
官方文檔:閉包