本文主要分析閉包以及閉包捕獲變量的原理
閉包
閉包是一個(gè)捕獲了全局上下文的常量或者變量的函數(shù),通俗來講,閉包可以是常量也可以是函數(shù)
- 【全局函數(shù)是一種特殊的閉包】:定義一個(gè)全局函數(shù),只是當(dāng)前的全局函數(shù)并不捕獲值
func test(){
print("test")
}
-
【函數(shù)閉包】:下面的函數(shù)是一個(gè)閉包,函數(shù)中的
incrementer是一個(gè)內(nèi)嵌函數(shù),可以從makeIncrementer中捕獲變量runningTotal
func makeIncrementer() -> () -> Int{
var runningTotal = 10
//內(nèi)嵌函數(shù),也是一個(gè)閉包
func incrementer() -> Int{
runningTotal += 1
return runningTotal
}
return incrementer
}
-
【閉包表達(dá)式 / 匿名函數(shù)】:下面是一個(gè)
閉包表達(dá)式,即一個(gè)匿名函數(shù),而且是從上下文中捕獲變量和常量
//閉包表達(dá)式
{ (param) -> ReturnType in
//方法體
}
使用閉包的好處
1、利用上下文推斷參數(shù)和返回值類型
2、單表達(dá)式可以隱式返回,即省略return關(guān)鍵字
3、參數(shù)名稱的簡(jiǎn)寫,例如 $0表示第一個(gè)參數(shù)
4、尾隨閉包表達(dá)式
閉包表達(dá)式
OC與swift的對(duì)比
-
OC中的
Block其實(shí)是一個(gè)匿名函數(shù),需要具備以下特點(diǎn):1、作用域 {}
2、參數(shù)和返回值
3、函數(shù)體(in)之后的代碼
swift中的閉包,可以當(dāng)做變量,也可以當(dāng)做參數(shù)傳遞
var clourse: (Int)->(Int) = { (age: Int) in
return age
}
閉包表達(dá)式的使用方式
- 【可選類型的閉包表達(dá)式】1、將閉包表達(dá)式聲明成一個(gè)可選類型
//聲明一個(gè)可選類型的閉包
<!--錯(cuò)誤寫法-->
var clourse: (Int) -> Int?
clourse = nil
<!--正確寫法-->
var clourse: ((Int) -> Int)?
clourse = nil
-
【閉包常量】2、通過
let將閉包聲明成一個(gè)常量(即一旦賦值之后就不能更改)
//2、通過let將閉包聲明為一個(gè)常量,即一旦賦值后就不能改變了
let clourse: (Int) -> Int
clourse = {(age: Int) in
return age
}
//報(bào)錯(cuò):Immutable value 'clourse' may only be initialized once
clourse = {(age: Int) in
return age
}
【閉包參數(shù)】3、將閉包作為 函數(shù)的參數(shù)
//3、將閉包作為函數(shù)的參數(shù)
func test(param: () -> Int){
print(param())
}
var age = 10
test { () -> Int in
age += 1
return age
}
尾隨閉包
當(dāng)閉包作為函數(shù)的最后一個(gè)參數(shù),如果當(dāng)前的閉包表達(dá)式很長(zhǎng),我們可以通過尾隨閉包的書寫方法來提高代碼的可讀性
//閉包表達(dá)式作為函數(shù)的最后一個(gè)參數(shù)
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool{
return by(a, b, c)
}
//常規(guī)寫法
test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
return (item1 + item2 < item3)
})
//尾隨閉包寫法
test(10, 20, 30) { (item1, item2, item3) -> Bool in
return (item1 + item2 < item3)
}
- 我們平常使用的
array.sorted其實(shí)就是一個(gè)尾隨閉包,且這個(gè)函數(shù)就只有一個(gè)參數(shù),如下所示
//array.sorted就是一個(gè)尾隨閉包
var array = [1, 2, 3]
//1、完整寫法
array.sorted { (item1: Int, item2: Int) -> Bool in return item1 < item2}
//2、省略參數(shù)類型:通過array中的參數(shù)推斷類型
array.sorted { (item1, item2) -> Bool in return item1 < item2}
//3、省略參數(shù)類型 + 返回值類型:通過return推斷返回值類型
array.sorted { (item1, item2) in return item1 < item2}
//4、省略參數(shù)類型 + 返回值類型 + return關(guān)鍵字:?jiǎn)伪磉_(dá)式可以隱士表達(dá),即省略return關(guān)鍵字
array.sorted { (item1, item2) in item1 < item2}
//5、參數(shù)名稱簡(jiǎn)寫
array.sorted {return $0 < $1}
//6、參數(shù)名稱簡(jiǎn)寫 + 省略return關(guān)鍵字
array.sorted {$0 < $1}
//7、最簡(jiǎn):直接傳比較符號(hào)
array.sorted (by: <)
捕獲一個(gè)變量
下面代碼的打印結(jié)果是什么?
func makeIncrementer() -> () -> Int{
var runningTotal = 10
//內(nèi)嵌函數(shù),也是一個(gè)閉包
func incrementer() -> Int{
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
print(makeInc())
print(makeInc())
print(makeInc())
<!--打印結(jié)果-->
11
12
13
打印結(jié)果如下,從結(jié)果中可以看出,每次的結(jié)構(gòu)都是在上次函數(shù)執(zhí)行的基礎(chǔ)上累加的,但是我們所知的runningTotal是一個(gè)臨時(shí)變量,按理說每次進(jìn)入函數(shù)都是10,這里為什么會(huì)每次累加呢? 主要原因:內(nèi)嵌函數(shù)捕獲了runningTotal,不再是單純的一個(gè)變量了
- 如果是下面這種方式調(diào)用呢?
print(makeIncrementer()())
print(makeIncrementer()())
print(makeIncrementer()())
<!--打印結(jié)果-->
11
11
11
為什么這種方式每次打印的結(jié)果就是同一個(gè)呢?
1、SIL分析
將上述代碼通過SIL分析:
1、通過
alloc_box申請(qǐng)了一個(gè)堆上的引用計(jì)數(shù),并將引用計(jì)數(shù)地址給了RunningTotal,將變量存儲(chǔ)到堆上2、通過
project_box從堆上取出變量-
3、將取出的變量交給閉包進(jìn)行調(diào)用
結(jié)論:所以,捕獲值的本質(zhì)是
將變量存儲(chǔ)到堆上
2、斷點(diǎn)驗(yàn)證
-
也可以通過斷點(diǎn)來驗(yàn)證,在
makeIncrementer方法內(nèi)部調(diào)用了swift_allocObject方法image
總結(jié)
一個(gè)閉包能夠從上下文捕獲已經(jīng)定義的常量和變量,即使這些定義的常量和變量的原作用域不存在,閉包仍然能夠在其函數(shù)體內(nèi)引用和修改這些值
當(dāng)每次
修改捕獲值時(shí),修改的是堆區(qū)中的value值當(dāng)每次
重新執(zhí)行當(dāng)前函數(shù)時(shí),都會(huì)重新創(chuàng)建內(nèi)存空間
所以上面的案例中我們知道:
makeInc是用于存儲(chǔ)makeIncrementer函數(shù)調(diào)用的全局變量,所以每次都需要依賴上一次的結(jié)果而直接調(diào)用函數(shù)時(shí),相當(dāng)于每次都新建一個(gè)堆內(nèi)存,所以每次的結(jié)果都是不關(guān)聯(lián)的,即每次結(jié)果都是一致的
閉包是引用類型
這里還要一個(gè)疑問,makeInc存儲(chǔ)的到底是什么?個(gè)人猜測(cè)存儲(chǔ)的是runningTotal的堆區(qū)地址,下面我們通過分析來驗(yàn)證是否如此
但是此時(shí)我們發(fā)現(xiàn),通過SIL并沒有辦法分析出什么,那么可以將SIL降一級(jí),通過IR代碼來觀察數(shù)據(jù)的構(gòu)成
在分析之前,首先來了解下IR的基本語法
IR基本語法
- 通過以下命令將代碼轉(zhuǎn)換為IR文件
swiftc -emit-ir 文件名 > ./main.ll && code main.ll
例如:
- cd 文件所在路徑
- swiftc -emit-ir main.swift > ./main.ll && open main.ll
- 數(shù)組
/*
- elementnumber 數(shù)組中存放數(shù)據(jù)的數(shù)量
- elementtype 數(shù)組中存放數(shù)據(jù)的類型
*/
[<elementnumber> x <elementtype>]
<!--舉例-->
/*
24個(gè)i8都是0
- iN:表示多少位的整型,即8位的整型 - 1字節(jié)
*/
alloca [24 x i8], align 8
- 結(jié)構(gòu)體
/*
- T:結(jié)構(gòu)體名稱
- <type list> :列表,即結(jié)構(gòu)體的成員列表
*/
//和C語言的結(jié)構(gòu)體類似
%T = type {<type list>}
<!--舉例-->
/*
- swift.refcounted:結(jié)構(gòu)體名稱
- %swift.type*:swift.type指針類型
- i64:64位整型 - 8字節(jié)
*/
%swift.refcounted = type { %swift.type*, i64}
- 指針類型
<type> *
<!--舉例-->
//64位的整型 - 8字節(jié)
i64*
-
getelementptr指令
在LLVM中獲取數(shù)組和結(jié)構(gòu)體的成員時(shí)通過getelementptr,語法規(guī)則如下:
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
<!--舉例-->
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[]) {
munge(array);
return 0;
}
通過下面的命令將c/c++編譯成IR
clang -S -emit-llvm 文件名 > ./main.ll && code main.ll
<!--舉例-->
clang -S -emit-llvm ${SRCROOT}/06-EnumTestC/main.c > ./main.ll && code main.ll

- 第一個(gè)索引:
%struct.munger_struct* %13, i32 0等價(jià)于第一個(gè)索引類型 + 第一個(gè)索引值==》 共同決定第一個(gè)索引的偏移量 - 第二個(gè)索引:
i32 0
再結(jié)合圖來理解
int main(int argc, const char * argv[]) {
int array[4] = {1, 2, 3, 4};
int a = array[0];
return 0;
}
其中int a = array[0];這句對(duì)應(yīng)的LLVM代碼應(yīng)該是這樣的:
/*
- [4 x i32]* array:數(shù)組首地址
- 第一個(gè)0:相對(duì)于數(shù)組自身的偏移,即偏移0字節(jié) 0 * 4字節(jié)
- 第二個(gè)0:相對(duì)于數(shù)組元素的偏移,即結(jié)構(gòu)體第一個(gè)成員變量 0 * 4字節(jié)
*/
a = getelementptr inbounds [4 x i32], [4 x i32]* array, i64 0, i64 0
可以看到其中的第一個(gè)
0,使用基本類型[4 x i32],因此返回的指針前進(jìn)0 * 16字節(jié),即當(dāng)前數(shù)組首地址-
第二個(gè)
index,使用基本類型i32,返回的指針前進(jìn)0字節(jié),即當(dāng)前數(shù)組的第一個(gè)元素,返回的指針類型是i32*image
總結(jié)
第一個(gè)索引不會(huì)改變返回的指針的類型,即
ptrval前面的*對(duì)應(yīng)什么類型,返回的就是什么類型第一個(gè)索引的
偏移量是由第一個(gè)索引的值和第一個(gè)ty指定的基本類型共同確定的后面的索引是在數(shù)組或者結(jié)構(gòu)體內(nèi)進(jìn)行索引
每增加一個(gè)索引,就會(huì)使得該
索引使用的基本類型和返回的指針類型去掉一層(例如 [4 x i32] 去掉一層是 i32)
IR分析
分析IR代碼
-
查看
makeIncrementer方法- 1、首先通過
swift_allocObject創(chuàng)建swift.refcounted結(jié)構(gòu)體 - 2、然后將
swift.refcounted轉(zhuǎn)換為<{ %swift.refcounted, [8 x i8] }>*結(jié)構(gòu)體(即Box) - 3、取出結(jié)構(gòu)體中index等于1的成員變量,存儲(chǔ)到
[8 x i8]*連續(xù)的內(nèi)存空間中 - 4、將內(nèi)嵌函數(shù)的地址存儲(chǔ)到i8即void地址中
- 5、最后返回一個(gè)結(jié)構(gòu)體
- 1、首先通過
- makeIncrementer函數(shù)-IR分析1
其結(jié)構(gòu)體定義如下
- makeIncrementer函數(shù)-結(jié)構(gòu)體定義
仿寫
通過上述的分析,仿寫其內(nèi)部的結(jié)構(gòu)體,然后構(gòu)造一個(gè)函數(shù)的結(jié)構(gòu)體,將makeInc的地址綁定到結(jié)構(gòu)體中
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
//函數(shù)返回值結(jié)構(gòu)體
//BoxType 是一個(gè)泛型,最終是由傳入的Box決定的
struct FunctionData<BoxType>{
//內(nèi)嵌函數(shù)地址
var ptr: UnsafeRawPointer
var captureValue: UnsafePointer<BoxType>
}
//捕獲值的結(jié)構(gòu)體
struct Box<T> {
var refCounted: HeapObject
var value: T
}
//封裝閉包的結(jié)構(gòu)體,目的是為了使返回值不受影響
struct VoidIntFun {
var f: () ->Int
}
//下面代碼的打印結(jié)果是什么?
func makeIncrementer() -> () -> Int{
var runningTotal = 10
//內(nèi)嵌函數(shù),也是一個(gè)閉包
func incrementer() -> Int{
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = VoidIntFun(f: makeIncrementer())
let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
//初始化的內(nèi)存空間
ptr.initialize(to: makeInc)
//將ptr重新綁定內(nèi)存
let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) {
$0.pointee
}
print(ctx.ptr)
print(ctx.captureValue.pointee)
<!--打印結(jié)果-->
0x0000000100002bc0
Box<Int>(refCounted: _7_Clourse.HeapObject(type: 0x0000000100004038, refCount1: 3, refCount2: 2), value: 10)
- 終端命令查找
0000000100002bc0(其中0x0000000100002bc0是內(nèi)嵌函數(shù)的地址)
nm -p /Users/chenjialin/Library/Developer/Xcode/DerivedData/07、Clourse-bsccpnlhsrkbzkdglsojfgisewnx/Build/Products/Debug/07、Clourse | grep 0000000100002bc0
其中s10_7_Clourse15makeIncrementerSiycyF11incrementerL_SiyFTA是內(nèi)嵌函數(shù)的地址對(duì)應(yīng)的符號(hào)
結(jié)論:所以當(dāng)我們var makeInc2 = makeIncrementer()使用時(shí),相當(dāng)于給makeInc2就是FunctionData結(jié)構(gòu)體,其中關(guān)聯(lián)了內(nèi)嵌函數(shù)地址,以及捕獲變量的地址,所以才能在上一個(gè)的基礎(chǔ)上進(jìn)行累加
捕獲兩個(gè)變量的情況
上面的案例中,我們分析了閉包捕獲一個(gè)變量的情況,如果是將捕獲一個(gè)變量更改為捕獲兩個(gè)變量呢?如下所示修改makeIncrementer函數(shù)
func makeIncrementer(forIncrement amount: Int) -> () -> Int{
var runningTotal = 0
//內(nèi)嵌函數(shù),也是一個(gè)閉包
func incrementer() -> Int{
runningTotal += amount
return runningTotal
}
return incrementer
}
-
查看其IR代碼
內(nèi)部結(jié)構(gòu)仿寫
根據(jù)捕獲一個(gè)變量的仿寫,繼續(xù)仿寫捕獲兩個(gè)變量的情況
//2、閉包捕獲多個(gè)值的原理
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
//函數(shù)返回值結(jié)構(gòu)體
//BoxType 是一個(gè)泛型,最終是由傳入的Box決定的
struct FunctionData<BoxType>{
var ptr: UnsafeRawPointer//內(nèi)嵌函數(shù)地址
var captureValue: UnsafePointer<BoxType>
}
//捕獲值的結(jié)構(gòu)體
struct Box<T> {
var refCounted: HeapObject
var value: T
}
//封裝閉包的結(jié)構(gòu)體,目的是為了使返回值不受影響
struct VoidIntFun {
var f: () ->Int
}
//下面代碼的打印結(jié)果是什么?
func makeIncrementer(forIncrement amount: Int) -> () -> Int{
var runningTotal = 0
//內(nèi)嵌函數(shù),也是一個(gè)閉包
func incrementer() -> Int{
runningTotal += amount
return runningTotal
}
return incrementer
}
var makeInc = makeIncrementer(forIncrement: 10)
var f = VoidIntFun(f: makeInc)
let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
//初始化的內(nèi)存空間
ptr.initialize(to: f)
//將ptr重新綁定內(nèi)存
let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) {
$0.pointee
}
print(ctx.ptr)
print(ctx.captureValue)
<!--打印結(jié)果-->
0x0000000100002910
0x00000001040098e0
通過終端命令查看第一個(gè)地址是否是內(nèi)嵌函數(shù)的地址
- 經(jīng)過包裝的內(nèi)嵌函數(shù)地址
注:(函數(shù)必須使用VoidIntFun包裝下,否則轉(zhuǎn)換后的地址不是內(nèi)嵌函數(shù)的地址),如下所示
- 未經(jīng)過包裝
通過cat查看 第一個(gè)地址,即內(nèi)嵌函數(shù)的地址
x/8g 第二個(gè)地址
繼續(xù)查看內(nèi)存情況
如果將runningTotal改成12呢?來驗(yàn)證是否如我們猜想的一樣。事實(shí)證明,確實(shí)是存儲(chǔ)的runningTotal
所以,閉包捕獲兩個(gè)變量時(shí),Box結(jié)構(gòu)體內(nèi)部發(fā)生了變化,修改后的仿寫代碼如下:
//2、閉包捕獲多個(gè)值的原理
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
//函數(shù)返回值結(jié)構(gòu)體
//BoxType 是一個(gè)泛型,最終是由傳入的Box決定的
struct FunctionData<BoxType>{
var ptr: UnsafeRawPointer//內(nèi)嵌函數(shù)地址
var captureValue: UnsafePointer<BoxType>
}
//捕獲值的結(jié)構(gòu)體
struct Box<T> {
var refCounted: HeapObject
//valueBox用于存儲(chǔ)Box類型
var valueBox: UnsafeRawPointer
var value: T
}
//封裝閉包的結(jié)構(gòu)體,目的是為了使返回值不受影響
struct VoidIntFun {
var f: () ->Int
}
//下面代碼的打印結(jié)果是什么?
func makeIncrementer(forIncrement amount: Int) -> () -> Int{
var runningTotal = 12
//內(nèi)嵌函數(shù),也是一個(gè)閉包
func incrementer() -> Int{
runningTotal += amount
return runningTotal
}
return incrementer
}
var makeInc = makeIncrementer(forIncrement: 10)
var f = VoidIntFun(f: makeInc)
let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
//初始化的內(nèi)存空間
ptr.initialize(to: f)
//將ptr重新綁定內(nèi)存
let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int, Int>>.self, capacity: 1) {
$0.pointee
}
print(ctx.ptr)
print(ctx.captureValue.pointee)
print(ctx.captureValue.pointee.valueBox)
<!--打印結(jié)果-->
0x0000000100002b30
Box<Int>(refCounted: _7_Clourse.HeapObject(type: 0x0000000100004090, refCount1: 3, refCount2: 4), valueBox: 0x00000001006094a0, value: 10)
0x00000001006094a0
疑問:如果是捕獲3個(gè)變量呢?
-
如下所示,是捕獲三個(gè)值的內(nèi)存情況
通過IR文件發(fā)現(xiàn),從
返回值倒推
<!--返回值-->
ret { i8*, %swift.refcounted* } %15
<!--%15-->
%15 = insertvalue { i8*, %swift.refcounted* }
{ i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementer12forIncrement7amount2SiycSi_SitF11incrementerL_SiyFTA" to i8*),
%swift.refcounted* undef }, %swift.refcounted* %10, 1
<!--%10-->
//與捕獲兩個(gè)變量相比,區(qū)別在于 i64 32 變成了 i64 40
%10 = call noalias %swift.refcounted* @swift_allocObject(
%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata.3, i32 0, i32 2),
i64 40, i64 7) #1
所以Box結(jié)構(gòu)體改為
//捕獲值的結(jié)構(gòu)體
struct Box<T> {
var refCounted: HeapObject
//這也是一個(gè)HeapObject
var valueBox: UnsafeRawPointer
var value1: T
var value2: T
}
最終完整的仿寫代碼為
//3、捕獲3個(gè)值
struct HeapObject {
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
//函數(shù)返回值結(jié)構(gòu)體
//BoxType 是一個(gè)泛型,最終是由傳入的Box決定的
struct FunctionData<BoxType>{
var ptr: UnsafeRawPointer//內(nèi)嵌函數(shù)地址
var captureValue: UnsafePointer<BoxType>
}
//捕獲值的結(jié)構(gòu)體
struct Box<T> {
var refCounted: HeapObject
//valueBox用于存儲(chǔ)Box類型
var valueBox: UnsafeRawPointer
var value1: T
var value2: T
}
//封裝閉包的結(jié)構(gòu)體,目的是為了使返回值不受影響
struct VoidIntFun {
var f: () ->Int
}
//下面代碼的打印結(jié)果是什么?
func makeIncrementer(forIncrement amount: Int, amount2: Int) -> () -> Int{
var runningTotal = 1
//內(nèi)嵌函數(shù),也是一個(gè)閉包
func incrementer() -> Int{
runningTotal += amount
runningTotal += amount2
return runningTotal
}
return incrementer
}
var makeInc = makeIncrementer(forIncrement: 10, amount2: 2)
var f = VoidIntFun(f: makeInc)
let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
//初始化的內(nèi)存空間
ptr.initialize(to: f)
//將ptr重新綁定內(nèi)存
let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) {
$0.pointee
}
print(ctx.ptr)
print(ctx.captureValue.pointee.value1)
print(ctx.captureValue.pointee.value2)
<!--打印結(jié)果-->
10
2
從打印結(jié)果可以看出,正好是傳入的兩個(gè)參數(shù)值
總結(jié)
1、捕獲值原理:
在堆上開辟內(nèi)存空間,并將捕獲的值放到這個(gè)內(nèi)存空間里2、修改捕獲值時(shí):實(shí)質(zhì)是
修改堆空間的值3、
閉包是一個(gè)引用類型(引用類型是地址傳遞),閉包的底層結(jié)構(gòu)(是結(jié)構(gòu)體:函數(shù)地址 + 捕獲變量的地址 == 閉包)4、
函數(shù)也是一個(gè)引用類型(本質(zhì)是一個(gè)結(jié)構(gòu)體,其中只保存了函數(shù)的地址),例如還是以makeIncrementer函數(shù)為例
func makeIncrementer(inc: Int) -> Int{
var runningTotal = 1
return runningTotal + inc
}
var makeInc = makeIncrementer
分析其IR代碼,函數(shù)在傳遞過程中,傳遞的就是函數(shù)的地址
將仿寫的FunctionData進(jìn)行修改
struct FunctionData{
var ptr: UnsafeRawPointer//內(nèi)嵌函數(shù)地址
var captureValue: UnsafePointer<BoxType>
}
然后改版后的結(jié)構(gòu)仿寫如下
//函數(shù)也是引用類型
struct FunctionData{
//函數(shù)地址
var ptr: UnsafeRawPointer
var captureValue: UnsafeRawPointer?
}
//封裝閉包的結(jié)構(gòu)體,目的是為了使返回值不受影響
struct VoidIntFun {
var f: (Int) ->Int
}
func makeIncrementer(inc: Int) -> Int{
var runningTotal = 1
return runningTotal + inc
}
var makeInc = makeIncrementer
var f = VoidIntFun(f: makeInc)
let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
//初始化的內(nèi)存空間
ptr.initialize(to: f)
//將ptr重新綁定內(nèi)存
let ctx = ptr.withMemoryRebound(to: FunctionData.self, capacity: 1) {
$0.pointee
}
print(ctx.ptr)
print(ctx.captureValue)
<!--打印結(jié)果-->
0x0000000100003370
nil
通過cat命令查看該地址,地址就是makeIncrementer函數(shù)的地址
總結(jié)
-
一個(gè)閉包能夠
從上下文中捕獲已經(jīng)定義的常量/變量,即使其作用域不存在了,閉包仍然能夠在其函數(shù)體內(nèi)引用、修改1、每次
修改捕獲值:本質(zhì)修改的是堆區(qū)中的value值2、每次
重新執(zhí)行當(dāng)前函數(shù),會(huì)重新創(chuàng)建新的內(nèi)存空間
捕獲值原理:本質(zhì)是在堆區(qū)開辟內(nèi)存空間,并將捕獲值存儲(chǔ)到這個(gè)內(nèi)存空間閉包是一個(gè)引用類型(本質(zhì)是
函數(shù)地址傳遞),底層結(jié)構(gòu)為:閉包 = 函數(shù)地址 + 捕獲變量的地址函數(shù)也是引用類型(本質(zhì)是
結(jié)構(gòu)體,其中保存了函數(shù)的地址)
















