函数类型
在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