大家好,我是考100分的小小码 ,祝大家学习进步,加薪顺利呀。今天说一说swift闭包详解_swift闭包,希望您对编程的造诣更进一步.
函数类型
在Swift中,函数和其他数据类型拥有一样的地位,函数不仅可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。 所以函数也有自己的类型,当我们使用函数作为变量的时候,如果有同名函数,那么当你指定函数类型的时候,编译器就不会报错。
当函数赋值给一个变量时,我们来看一下这个变量里面存储了什么? 可以看到,函数类型也是一个引用类型,而且和其它数据结构一样,也有自己的Metadata。我们可以从源码里面去探个究竟。 通过源码我们可以知道,TargetFunctionTypeMetadata
继承自 TargetMetadata
,因此,会有一个Kind
属性。于此同时,它还有一个Flags
属性和用来标识返回值类型的ResultType
属性。另外,这个类里面还有一个连续的内存数组空间用来存放参数列表。可以看到,参数列表里面存放的都是TargetMetadata
类型。也就是说,函数里面的参数类型和返回值类型是Any.Type
类型。
接下来我们来看下TargetFunctionTypeFlags
类,这是存储函数类型的标志位的类。可以在这个类里面获取到函数参数个数。
class TargetFunctionTypeFlags {
enum : int_type {
NumParametersMask = 0x0000FFFFU,
ConventionMask = 0x00FF0000U,
ConventionShift = 16U,
ThrowsMask = 0x01000000U,
ParamFlagsMask = 0x02000000U,
EscapingMask = 0x04000000U,
DifferentiableMask = 0x08000000U,
GlobalActorMask = 0x10000000U,
AsyncMask = 0x20000000U,
SendableMask = 0x40000000U,
// NOTE: The next bit will need to introduce a separate flags word.
};
int_type Data;
public:
unsigned getNumParameters() const { return Data & NumParametersMask; }
从上面的代码我们可以得到这个TargetFunctionTypeMetadata
的结构
struct TargetFunctionTypeMetadata {
var kind: Int
var flags: Int
var resultType: Any.Type
var arguments:ArgumentsBuffer<Any.Type>
func numberArguments() -> Int {
return self.flags & 0x0000FFFF
}
}
struct ArgumentsBuffer<Element>{
var element: Element
mutating func buffer(n: Int) -> UnsafeBufferPointer<Element> {
return withUnsafePointer(to: &self) {
let ptr = $0.withMemoryRebound(to: Element.self, capacity: 1) { start in
return start
}
return UnsafeBufferPointer(start: ptr, count: n)
}
}
mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self {
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
}
}
}
接下来,我们根据上面的数据结构来获得一个函数的参数个数、返回值、以及参数类型。
func addTwoInts(_ a: Int, _ b: String) -> Bool {
return true
}
let value = type(of: addTwoInts)
let functionType = unsafeBitCast(value as Any.Type to: UnsafeMutablePointer<TargetFunctionTypeMetadata>.self)
let numberOfArguments = functionType.pointee.numberArguments()
print("该函数\(value) 有 \(numberOfArguments) 个参数")
let returnType = functionType.pointee.resultType
print("该函数\(value) 的返回类型是 \(returnType)")
for i in 0..<numberOfArguments {
let argumentType = functionType.pointee.arguments.index(of: i).pointee
print("该函数\(value) 的第\(i+1)个参数的类型是\(argumentType)")
}
//打印结果
该函数(Int, String) -> Bool 有 2 个参数
该函数(Int, String) -> Bool 的返回类型是 Bool
该函数(Int, String) -> Bool 的第1个参数的类型是Int
该函数(Int, String) -> Bool 的第2个参数的类型是String
闭包
什么是闭包
闭包是一个捕获了上下文的常量或者是变量的函数。我们看一下官方给的示例。
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
在代码中,incrementer
作为一个闭包,也是一个函数。而且incrementer
的生命周期比makeIncrementer
要长。当makeIncrementer
执行完毕后,内部包含的变量runningTotal
也随之消失,但是incrementer
有可能还没执行。要想incrementer
能够执行,这是就需要捕获runningTotal
到incrementer
内部中,因此构成闭包有两个关键点,一个是函数,另外一个是能够捕获外部变量或者常量。
闭包表达式
在swift中,我们可以用以下的表达式来定义闭包
{ (param) -> (returnType) in
//do something
}
可以看到,闭包表达式由作用域、函数参数、返回值、关键字in、函数体构成。
闭包表达式语法能够使用常量形式参数、变量形式参数和输入输出形式参数,但不能提供默认值。可变形式参数也能使用,但需要在形式参数列表的最后面使用。元组也可被用来作为形式参数和返回类型。
闭包的函数整体部分由关键字in
导入,这个关键字表示闭包的形式参数类型和返回类型定义已经完成,并且闭包的函数体即将开始。
闭包的使用
在Swift中,闭包可以当做变量使用,也可以当做函数的参数传递。
- 闭包当做变量
var closure : (Int) -> Int = { (age: Int) in
return age
}
- 闭包声明成一个可选类型
var closure : ((Int) -> Int)?
closure = nil
- 闭包当做一个常量(一旦赋值之后就不能改变了)
let closure: (Int) -> Int
closure = {(age: Int) in
return age
}
- 闭包当做函数参数
func test(param : () -> Int){
print(param())
}
var age = 10
test { () -> Int in
age += 1
return age
}
尾随闭包
当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很长,我们可以通过尾随闭包的书写方式来提高代码的可读性。
我们首先定义一个函数
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) ->Bool) -> Bool{
return by(a, b, c)
}
- 未使用尾随闭包
test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
return (item1 + item2 < item3)
})
- 使用尾随闭包
test(10, 20, 30){(_ item1: Int, _ item2: Int, _ item3: Int) in
return item1 + item2 < item3
}
闭包表达式简写
在swift中,使用闭包表达式能更简洁的传达信息。但是也不能太过简略。
var array = [1, 2, 3]
array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })
- 利用上下文推断参数和返回值类型
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
array.sort(by: {(item1, item2) in return item1 < item2 })
- 单表达式可以隐士返回,既省略
return
关键字
array.sort{(item1, item2) in item1 < item2 }
- 参数名称的简写(比如
$0
)
array.sort{ return $0 < $1 }
array.sort{ $0 < $1 }
- 尾随闭包表达式
array.sort(by: <)
闭包捕获值
闭包捕获一个全局变量
我们首先来看一下闭包捕获全局变量的情况,代码如下
var i = 1
let closure = {
print("closure\(i)")
}
i += 1
print("before closure \(i)")
closure()
print("after closure \(i)")
//打印结果
before closure 2
closure2
after closure 2
可以看到,i
的值发生变化后,closure
里面的i
也发生了变化。和OC里面的block
很像。接下来我们通过sil文件来探究一下。 通过上面的sil源码我们可以知道,当执行到closure
闭包的时候,直接去寻找变量i
的地址,然后把i
的值取出来。而此时i
的值已经发生了变化,因此取出来i
的值就是2。
也就是说此时闭包是直接拿到了全局变量去修改值,那么这里应该就不能叫捕获全局变量了,因为根本没有对全局变量做额外的操作。
闭包捕获一个局部变量
当闭包捕获一个局部变量时,内部又进行了哪些操作呢? 我们拿官方的例子验证一下。
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
然后我们把它转换成sil文件,可以看到makeIncrementer()
函数构成如下:
可以看到,在makeIncrementer()
函数中,使用了alloc_box
,而在闭包incrementer()
中,又使用到了project_box
。我们去官方文档里面去找这两个命令的定义。 在官方文档中可以看到,alloc_box
是在堆空间里面创建实例对象,project_box
是从这个实例对象地址中取出其中的值。所以闭包捕获变量其实是在堆空间里面创建一个实例对象,并且把捕获变量的值存储到这个实例对象中,每次调用闭包使用的都是同一个堆空间的实例变量地址,所以在闭包外面修改值,闭包内部的值也会改变。 通过lldb打印makeInc
,我们也可以看到,闭包里面也有Metadata
闭包的本质
这次我们需要通过分析IR
文件来探究闭包的本质,首先我们要了解一下IR
语法
IR语法
i8
:Int8
或者void *
,i16
:Int16
,i32
:Int32
,i64
:Int64
,void *
- 数组
[<elementnumber> x <elementtype>]
//example
alloc [24 x i8], align 8 24个i8都是0
alloc [4 x i32] === array
- 结构体
%T = type {<type list>}
//swift.refcount 类型的结构体 有两个成员,分别为swift.type* 类型 和 i64 类型。
%swift.refcount = type {%swift.type*,i64}
- 指针
<type> *
// Int64位的整形指针
i64*
getelementptr
在LLVM中获取数组或结构体的成员,语法规则如下
<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;
};
//获取结构体munger_struct的内存首地址
//i64 0 取出的是 struct.munger_struct类型的指针,指针指向的是内存首地址
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i64 0
// 获取munger_struct 的第一个元素
// i64 0 取出的是 struct.munger_struct类型的指针,指针指向的是内存首地址
// i32 0取出的是 struct.munger_struct结构体中的第一个元素
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i64 0, i32 0
// 获取 munger_struct 第二个元素
// i64 0 取出的是 struct.munger_struct类型的指针,指针指向的是内存首地址
// i32 1取出的是 struct.munger_struct结构体中的第二个元素
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i64 0, i32 1
int main(int argc, const char * argv[]) {
int array[4] = {1, 2, 3, 4};
int a = array[0];
return 0;
}
其中 int a = array[0] 这句对应的LLVM代码应该是这样的:
a = getelementptr inbounds [4 x i32], [4 x i32]* array, i64 0, i32 0
- [4 x i32]* array:返回一个数组的指针。
- 第一个0:指针指向数组的首地址。
- 第二个0:相对于数组元素的偏移,即数组第一个成员变量。
对于getelementptr
,我们可以得到以下结论:
-
第一个索引不会改变返回的指针的类型,也就是说
ptrval
前面的*对应什么类型,返回就是什么类型 -
第一个索引的偏移量的是由第一个索引的值和第一个
ty
指定的基本类型共同确定的。 -
第二个索引以及后面的索引都是在数组或者结构体内进行索引
-
每增加一个索引,就会使得该索引使用的基本类型和返回的指针的类型去掉一层。
闭包的本质
接下来,我们使用IR来分析一下闭包,代码如下:
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
var makeInc = makeIncrementer()
转换成IR文件后,我们截取一些代码如下:
%swift.function = type { i8*, %swift.refcounted* }
%swift.refcounted = type { %swift.type*, i64 }
%swift.type = type { i64 }
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%TSi = type <{ i64 }>
//main函数
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
%3 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"() %4 = extractvalue { i8*, %swift.refcounted* } %3, 0 %5 = extractvalue { i8*, %swift.refcounted* } %3, 1 store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 0), align 8
store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 1), align 8 ret i32 0 }
从代码里面我们可以了解到,在main函数里面,先是去调用了makeIncrementer()
函数,返回了一个{ i8*, %swift.refcounted* }
的结构体,而%swift.refcounted
也是一个结构体{ %swift.type*, i64 }
,而%swift.type
又是一个{ i64 }
结构体,里面包含了一个64位整型的变量。
所以在main
函数中,其实是做了这么一个操作。调用makeIncrementer()
函数,返回了一个结构体,然后去把这个结构体的成员变量值都拿出来,接着给变量makeInc
创建一个{ i8*, %swift.refcounted* }
的内存空间,把取出来的成员变量都存到这个内存空间里面,也就是完成了一次赋值操作。
接下来我们去看一下makeIncrementer()
函数的IR代码,看下这个函数里面具体做了些什么。
define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"() #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 = 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 %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 = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0 store i64 10, i64* %._value, align 8 %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1 call void @swift_release(%swift.refcounted* %1) #1 %6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
ret { i8*, %swift.refcounted* } %6
}
我们来具体分析一下这个代码:
%runningTotal.debug = alloca %TSi*, align 8
%0 = bitcast %TSi** %runningTotal.debug to i8*
这里是给局部变量runningTotal
创建一个%TSi*
结构体,用来存放局部变量的值,然后使用一个i8
类型的指针指向这个结构体。
%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
这个代码是在堆空间里面创建了一个%swift.refcounted*
类型的实例变量。
%2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
把一个%swift.refcounted*
指针转换成{ %swift.refcounted, [8 x i8] }
结构体的指针。
%3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
这个代码是把指针指向<{ %swift.refcounted, [8 x i8] }>
结构体的索引值为1的元素。也就是[8 x i8]
数组。
%4 = bitcast [8 x i8]* %3 to %TSi*
store %TSi* %4, %TSi** %runningTotal.debug, align 8
这段代码是把指向[8 x i8]
数组的指针,转成%TSi*
指针,同时把局部变量runningTotal
的%TSi*
结构体存入这个数组里面。
%._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
store i64 10, i64* %._value, align 8
这个代码先去获取[8 x i8]
数组的第一个元素,也就是刚才存进去的局部变量runningTotal
的%TSi*
结构体,同时把值10
存入到这个结构体里面。
%5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
call void @swift_release(%swift.refcounted* %1) #1
这个代码里面做的是跟引用计数相关的,不做研究。
%6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1 ret { i8*, %swift.refcounted* } %6
上面这个代码就是把指向incrementer()
函数的指针转成i8*
,同时把刚才上面创建的保存着数据10
的%swift.refcounted*
类型的实例变量,一起插入到{ i8*, %swift.refcounted* }
结构体中。并把这个结构体返回出去。
通过上面的分析,我们可以把闭包在内存中的结构用以下结构体来表示:
struct ClosureData{
var ptr: UnsafeRawPointer // 函数地址
var object: HeapObject // 存储捕获堆空间地址的值
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
我们在SIL分析中看到,闭包有使用了alloc_box
来创建实例变量,同时我们知道闭包中%swift.refcounted*
类型的实例变量里面存储这捕获变量的值。因此,我们可以进一步还原object
属性变量
struct Box<T>{
var object: HeapObject
var value: T
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
所以闭包的最终内存结构可以这样表示:
struct ClosureData{
var ptr: UnsafeRawPointer // 函数地址
var object: UnsafePointer<Box>// 存储捕获堆空间地址的值
}
struct Box<T>{
var object: HeapObject
var value: T
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
验证闭包结构分析结果
接下来我们验证一下上面的结构体是否正确。验证代码如下:
struct NoMeanStruct {
var f: () -> Int
}
var f = NoMeanStruct(f: makeIncrementer())
let ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
ptr.initialize(to: f)
let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int>>.self, capacity: 1) {
$0.pointee
}
print("闭包函数的内存地址: \(ctx.ptr)")
print("闭包函数的堆空间地址: \(ctx.object)")
print("闭包函数的捕获变量值: \(ctx.object.pointee.value)")
//打印结果
闭包函数的内存地址: 0x0000000100008c80
闭包函数的堆空间地址: 0x0000000100748aa0
闭包函数的捕获变量值: 10
现在我们来验证这个结果 看结果和推断的结果完全一致。
捕获引用类型
如果闭包捕获的变量类型是一个引用类型,比如一个类的实例对象,那又是怎么捕获的呢?首先我们举一个例子
class LGTeacher {
var age = 10
}
func test() {
var t = LGTeacher()
let clousure = {
t.age += 10
}
clousure()
}
test()
转换成IR代码,我们看一下main
函数里面做了什么
define hidden swiftcc void @"$s4main4testyyF"() #0 { entry: %0 = alloca %T4main9LGTeacherC*, align 8 %1 = bitcast %T4main9LGTeacherC** %0 to i8* call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false) //创建了一个%swift.function结构体 %clousure.debug = alloca %swift.function, align 8 %2 = bitcast %swift.function* %clousure.debug to i8* call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 16, i1 false) %3 = bitcast %T4main9LGTeacherC** %0 to i8* call void @llvm.lifetime.start.p0i8(i64 8, i8* %3) %4 = call swiftcc %swift.metadata_response @"$s4main9LGTeacherCMa"(i64 0) #7
%5 = extractvalue %swift.metadata_response %4, 0
//调用了LGTeacher.__allocating_init()方法,也就是创建了一个实例对象t,并且把这个实例对象地址存储到了%6寄存器。
%6 = call swiftcc %T4main9LGTeacherC* @"$stmain9LGTeacherCACycfC"(%swift.type* swiftself %5) %7 = bitcast %T4main9LGTeacherC* %6 to %swift.refcounted* %8 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %7) #3 store %T4main9LGTeacherC* %6, %T4main9LGTeacherC** %0, align 8 %9 = bitcast %T4main9LGTeacherC* %6 to %swift.refcounted* %10 = bitcast %swift.function* %clousure.debug to i8* call void @llvm.lifetime.start.p0i8(i64 16, i8* %10) %clousure.debug.fn = getelementptr inbounds %swift.function, %swift.function* %clousure.debug, i32 0, i32 0 //把test函数的地址存入%clousure.debug.fn里面,也就是void*。 store i8* bitcast (void (%swift.refcounted*)* @"$s4main4testyyFyycfU_Tf2i_nTA" to i8*), i8** %clousure.debug.fn, align 8
%clousure.debug.data = getelementptr inbounds %swift.function, %swift.function* %clousure.debug, i32 0, i32 1
//把实例变量t的地址存到%clousure.debug.data中,因为实例变量t的地址已经在堆区,不需要重新建立。
store %swift.refcounted* %9, %swift.refcounted** %clousure.debug.data, align 8
//对实例对象进行引用计数的操作
%11 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %9) #3
//调用了clousure闭包
call swiftcc void @"$s4main4testyyFyycfU_Tf2i_nTA"(%swift.refcounted* swiftself %9) call void @swift_release(%swift.refcounted* %9) #3 call void @swift_release(%swift.refcounted* %9) #3 //把捕获的实例变量t的地址存储到闭包的%swift.refcounted结构体中 %toDestroy = load %T4main9LGTeacherC*, %T4main9LGTeacherC** %0, align 8 call void bitcast (void (%swift.refcounted*)* @swift_release to void (%T4main9LGTeacherC*)*)(%T4main9LGTeacherC* %toDestroy) #3 %12 = bitcast %T4main9LGTeacherC** %0 to i8* call void @llvm.lifetime.end.p0i8(i64 8, i8* %12) ret void }
从这里可以看出,在捕获引用类型时候,其实也不需要捕获实例对象,因为它已经在堆区了,就不需要再去创建一个堆空间的实例了,只需要将它的地址存储到闭包的结构中,操作实例对象的引用计数,就可以了。
接下来我们使用上面闭包的结构验证一下
class LGTeacher {
var age = 10
}
func test() {
let t = LGTeacher()
let clousure = {
t.age += 10
}
let f = NoMeanStruct(f: clousure)
let ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
ptr.initialize(to: f)
let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int>>.self capacity: 1) {
$0.pointee
}
print("闭包函数的内存地址: \(ctx.ptr)")
print("闭包函数的堆空间地址: \(ctx.object)")
print("闭包函数的捕获变量值: \(ctx.object.pointee.value)")
print("end")
}
test()
打印结果如下: 闭包存储的堆空间地址,就是捕获到的引用类型实例变量的地址。
闭包捕获多个变量
我们上面还原的是闭包捕获一个变量的情况,那如果闭包捕获多个变量,又是什么样的呢?我们继续分析两个变量的情况,代码如下:
func makeIncrementer(_ amount: Int) -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
var makeInc = makeIncrementer(10)
接下来我们把它编译成IR文件,makeIncrementer()
函数的IR代码如下:
define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerySiycSiF"(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) 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 %._value = getelementptr inbounds %TSi, %TSi* %6, i32 0, i32 0 store i64 10, i64* %._value, align 8 %7 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %3) #2 %8 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata.3, i32 0, i32 2), i64 32, i64 7) #2 %9 = bitcast %swift.refcounted* %8 to <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %10 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 1 store %swift.refcounted* %3, %swift.refcounted** %10, align 8 %11 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 2 %._value1 = getelementptr inbounds %TSi, %TSi* %11, i32 0, i32 0 store i64 %0, i64* %._value1, align 8 call void @swift_release(%swift.refcounted* %3) #2 %12 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerySiycSiF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %8, 1
ret { i8*, %swift.refcounted* } %12
}
现在我们来逐一分析一下:
%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)
store i64 %0, i64* %amount.debug, align 8
这上面的代码分别是给捕获的变量amount
和runningTotal
分别创建了一个内存空间,并放在了%1
和%2
寄存器里面。接着把0
存入变量amount
里面,因为amount
是外部传进来的变量,还不知道传值多少,因此默认数值为0
。
%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
%._value = getelementptr inbounds %TSi, %TSi* %6, i32 0, i32 0
store i64 10, i64* %._value, align 8
这里和上面的捕获单个变量的情形一样,创建一个%swift.refcounted*
结构体,并且把runningTotal
的内存地址存到这个结构体里面,并把10存到runningTotal
的内存地址里。
%8 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata.3, i32 0, i32 2), i64 32, i64 7) #2
%9 = bitcast %swift.refcounted* %8 to <{ %swift.refcounted, %swift.refcounted*, %TSi }>*
这里又新建了一个%swift.refcounted*
结构体,并且指针指向了{ %swift.refcounted, %swift.refcounted*, %TSi }
结构体。
%11 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 2
%._value1 = getelementptr inbounds %TSi, %TSi* %11, i32 0, i32 0
store i64 %0, i64* %._value1, align 8
call void @swift_release(%swift.refcounted* %3) #2
这里是把存有runningTotal
变量的%swift.refcounted*
结构体,存到了{ %swift.refcounted, %swift.refcounted*, %TSi }
结构体的第二个%swift.refcounted*
属性里,并对其引用计数进行释放操作。同时把amount
变量存入到%TSi
中。
%12 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerySiycSiF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %8, 1 ret { i8*, %swift.refcounted* } %12
这里把incrementer()
函数的内存地址,和指向{ %swift.refcounted, %swift.refcounted*, %TSi }
结构体的指针,共同构成了闭包的数据结构。
综上所述,这里将第一个捕获的值存储到堆区后,在捕获第二个值创建了新的对象,然后把第一个对象存储进新的对象里面。
所以,当闭包捕获到两个变量的时候,它的内存数据结构表示如下:
struct ClosureParamData<T>{
var ptr: UnsafeRawPointer // 函数地址
var captureValue: UnsafePointer<T>// 存储捕获堆空间地址的值
}
struct TwoParamerStruct<T1,T2>{
var object: HeapObject
var value1: UnsafePointer<Box<T1>>
var value2: T2
}
struct Box<T>{
var object: HeapObject
var value: T
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
接下来我们还原makeIncrementer()
函数的内存结构。
let f = NoMeanStruct(f: makeIncrementer(20))
let ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
ptr.initialize(to: f)
let ctx = ptr.withMemoryRebound(to: ClosureData<TwoParamerStruct<Int, Int>>.self, capacity: 1) {
$0.pointee
}
print("闭包函数的内存地址: \(ctx.ptr)")
print("闭包函数的堆空间地址: \(ctx.captureValue)")
print("闭包函数的第一个捕获变量值: \(ctx.captureValue.pointee.value1.pointee.value)")
print("闭包函数的第二个捕获变量值: \(ctx.captureValue.pointee.value2)")
//打印结果
闭包函数的内存地址: 0x0000000100008660
闭包函数的堆空间地址: 0x000000010113c430
闭包函数的第一个捕获变量值: 10
闭包函数的第二个捕获变量值: 20
闭包捕获三个变量
我们接着看闭包捕获三个变量的情况,代码如下:
func makeIncrementer(_ amount: Int, _ amount1: Int) -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += amount
runningTotal += amount1
return runningTotal
}
return incrementer
}
var makeInc = makeIncrementer(20,30)
编译成IR后,makeIncrementer()
函数的代码如下 可以看到,和捕获两个变量的代码差不多一样,不同的是结构体变成了{ %swift.refcounted, %swift.refcounted*, %TSi, %TSi }
,多了一个%TSi
用来存储捕获的第三个变量的值。
闭包捕获多个变量总结
通过上面闭包捕获两个和三个变量的分析,我们可以知道,闭包的数据结构有捕获单个变量和多个变量这两种情况。因此我们还原闭包数据结构的代码如下:
//单个值
struct ClosureData<Box>{
var ptr: UnsafeRawPointer // 函数地址
var captureValue: UnsafePointer<Box> // 存储捕获堆空间地址的值
}
//多个值
struct ClosureData<MutiValue>{
var ptr: UnsafeRawPointer // 函数地址
var captureValue: UnsafePointer<MutiValue> // 存储捕获堆空间地址的值
}
struct MutiValue<T1,T2......>{
var object: HeapObject
var value: UnsafePointer<Box<T1>>
var value: T2
var value: T3
.....
}
struct Box<T>{
var object: HeapObject
var value: T
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
逃逸闭包
逃逸闭包的定义:当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping
来明确闭包是允许逃逸的。
使用逃逸闭包一般满足以下两个条件:
- 当闭包被当作属性存储,导致函数完成时闭包生命周期被延长
- 当闭包异步执行,导致函数完成时闭包生命周期被延长。
- 可选类型的闭包默认是逃逸闭包。
以下这种闭包其实也是逃逸,对于编译器来说,把一个闭包赋值给了一个变量,编译器认为这个闭包可能会在其他地方去执行。
所以,逃逸闭包所需的条件:
- 作为函数的参数传递。
- 当前闭包在函数内部异步执行或者被存储。
- 函数结束,闭包被调用,闭包的生命周期未结束。
自动闭包
@autoclosure
是一种自动创建的闭包,用于将参数包装成闭包。这种闭包不接受任何参数,当它被调用的时候,会返回传入的值。这种便利语法让你在调用的时候能够省略闭包的花括号
函数中有一个 ()-> Any
类型的参数,用@autoclosure
修饰时,调用函数的时候可以传入一个确定的值 a
,这个值会被自动包装成(){return a}
的闭包,就不需要显示的将闭包表达式写出来
func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){
if condition {
print("debug:(message())")
}
}
debugOutPrint(true,"Application Error Occured" )
debugOutPrint(true, getString )
func getString()->String{
return "Application Error Occured"
}
defer关键字
defer {}
里的代码会在当前代码块返回的时候执行,无论当前代码块是从哪个分支return
的,即使程序抛出错误,也会执行。
如果多个defer
语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是先出现的后执行。
举一个简单例子
func f() {
defer { print("First defer") }
defer { print("Second defer") }
print("End of function")
}
f()
//打印结果
End of function
Second defer
First defer
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13207.html