在文章swift进阶(四)闭包的使用介绍了闭包的使用,那么闭包底层是什么样的呢?那今天让我们探究一番。
1. 闭包捕获上下文
先看以下代码打印的值应该是多少?
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
print(makeInc())
print(makeInc())
print(makeInc())
print("==========================")
print(makeIncrementer()())
print(makeIncrementer()())
print(makeIncrementer()())
打印结果如下:
查看SIL源码
-
执行
swiftc -emit-sil main.swift >> ./main.sil && code main.sil
,生成sil文件-
在sil文件中查看
makeIncrementer()
方法,会发下alloc_box
的地址赋值给了runningTotal变量。其中alloc_box
是在堆上开辟空间。 -
获取变量时,通过
project_box
在堆上取出来 -
在闭包引用前,执行了
retain
操作 -
在闭包引用后,执行了
release
操作
-
小结
- 闭包捕获值的本质是在堆上开辟一块空间,将当前变量的值放到堆空间上。
2. LR简单语法介绍
数组
- [数组的数量 x 数组存放的类型]
[<elementnumber> x <elementtype>]
//example
alloca[24xi8],align8 24个i8类型,
- in:多少位的整型,i8相当于8位,也就是1字节
结构体
- %结构体名称 = type {<结构体成员类型,成员大小>}
%swift.refcounted = type { %swift.type*, i64 }
指针类型
<type> *
//example
i64* //64的整型
getelementptr指令
获取数组、结构体中的元素
语法规则如下:
- 数组:返回值 = getelementptr 当前索引的基本类型, 当前索引的地址,当前数组的index
- 结构体:返回值 = getelementptr 当前索引的基本类型, 当前索引的地址,结构体指针的偏移量,当前数组的成员变量
<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;
}
生成的LR语法:
3. 闭包的底层
- 变量makeInc存放的是什么东西呢?
-
查看SIL源码,发现只有一个赋值,无法看到其中赋值的是什么。
-
那就生成LR源码查看
-
makeInc存放的就是一个结构体指针
- 其中结构体的成员变量有是i8 -> void *类型的指针
- %swift.refcounted* 是 i64类型,其大小是i64的结构体
-
翻译成swift代码:
struct FuntionData<T>{
var ptr: UnsafeRawPointer //
var captureValue: UnsafePointer<T>
}
-
查看结构体
{ i8*, %swift.refcounted* }
的赋值- 将
内嵌函数
的地址
存放到了i8*
中 - 将%1的值放到了index=1的位置
- 将
- 查看
%swift.refcounted*
的结构是 i64类型,其大小是i64的结构体 ,这个与HeapObject的结构有些相似
翻译成swift代码:
struct HeapObject{
var type: UnsafeRawPointer
var refCount1: UInt32
}
-
其中1%创建出来之后,有一层转换,
- 第一个成员变量是
%swift.refcounted
的地址 - 第二个成员变量
[8 x i8]
用来存储值
- 第一个成员变量是
翻译成swift代码:
struct Box<T> {
var refCounted: HeapObject
var value: T
}
- 根据上述的分析,将闭包的指针绑定到下列变量中,进行查看打印
struct HeapObject{
var type: UnsafeRawPointer
var refCount1: UInt32
}
//
struct FuntionData<T>{
var ptr: UnsafeRawPointer //
var captureValue: UnsafePointer<T>
}
struct Box<T> {
var refCounted: HeapObject
var value: T
}
//需要将当前闭包使用此结构体包装一层
struct VoidIntFun {
var f: () ->Int
}
func makeIncrementer() -> () -> Int {
var runningTotal = 12
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
var makeInc = makeIncrementer(forIncrement())
var f = VoidIntFun(f: makeInc)
let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
ptr.initialize(to: f)
let ctx = ptr.withMemoryRebound(to: FuntionData<Box<Int>>.self, capacity: 1){$0.pointee}
print(ctx.ptr)
print(ctx.captureValue)
- 查看打印结果
- 在终端查看
0x00000001000058c0
,运行nm -p 当前项目的可执行文件 | grep 00000001000058c0
思考:如果捕获的是多个变量呢,它的内存结构是什么样子的呢?
例如下面:
func makeIncrementer(forIncrement amount: Int,_ amount1: Int) -> () -> Int {
var runningTotal = 12
var thrid = 12
func incrementer() -> Int {
runningTotal += amount + thrid
return runningTotal
}
return incrementer
}
var makeInc = makeIncrementer(forIncrement: 10, 15)
使用上面分析的闭包原理打印下发现:
- 那么说明结构体Box就需要修改一下成员变量
struct Box<T> {
var refCounted: HeapObject
var valueBox: UnsafeRawPointer
var value: T
var value2: T
}
- 打印
ctx.captureValue.pointee
- 其完整代码如下:
struct HeapObject{
var type: UnsafeRawPointer
var refCount1: UInt32
var refCount2: UInt32
}
//
struct FuntionData<T>{
var ptr: UnsafeRawPointer //
var captureValue: UnsafePointer<T>
}
struct Box<T> {
var refCounted: HeapObject
var valueBox: UnsafeRawPointer
var value: T
var value2: T
}
struct VoidIntFun {
var f: () ->Int
}
func makeIncrementer(forIncrement amount: Int,_ amount1: Int) -> () -> Int {
var runningTotal = 12
var thrid = 12
func incrementer() -> Int {
runningTotal += amount + thrid
return runningTotal
}
return incrementer
}
var makeInc = makeIncrementer(forIncrement: 10, 15)
var f = VoidIntFun(f: makeInc)
let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
ptr.initialize(to: f)
let ctx = ptr.withMemoryRebound(to: FuntionData<Box<Int>>.self, capacity: 1){$0.pointee}
print(ctx.ptr)
print(ctx.captureValue.pointee)
小结
- 捕获值的原理是:在堆区开辟了一块内存空间,将捕获的值放到堆上
- 闭包是一个引用类型。