Swift进阶(八)—— 闭包

Swift进阶(八)—— 闭包

函数类型

Swift中,函数和其他数据类型拥有相同的位置,函数不只能够赋值给其他变量,也能够作为参数,传入另一个函数,或许作为别的函数的回来值。 所以函数也有自己的类型,当咱们运用函数作为变量的时分,假如有同名函数,那么当你指定函数类型的时分,编译器就不会报错。

Swift进阶(八)—— 闭包

当函数赋值给一个变量时,咱们来看一下这个变量里边存储了什么?

Swift进阶(八)—— 闭包
能够看到,函数类型也是一个引证类型,而且和其它数据结构相同,也有自己的Metadata。咱们能够从源码里边去探个终究。
Swift进阶(八)—— 闭包
经过源码咱们能够知道,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) -> Bool2 个参数
该函数(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能够履行,这是就需要捕获runningTotalincrementer内部中,因而构成闭包有两个关键点,一个是函数,别的一个是能够捕获外部变量或许常量

闭包表达式

在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文件来探究一下。

Swift进阶(八)—— 闭包
Swift进阶(八)—— 闭包
经过上面的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()函数构成如下:

Swift进阶(八)—— 闭包
Swift进阶(八)—— 闭包
能够看到,在makeIncrementer()函数中,运用了alloc_box,而在闭包incrementer()中,又运用到了project_box。咱们去官方文档里边去找这两个命令的界说。
Swift进阶(八)—— 闭包
Swift进阶(八)—— 闭包
在官方文档中能够看到,alloc_box是在堆空间里边创立实例目标,project_box是从这个实例目标地址中取出其间的值。所以闭包捕获变量其实是在堆空间里边创立一个实例目标,而且把捕获变量的值存储到这个实例目标中,每次调用闭包运用的都是同一个堆空间的实例变量地址,所以在闭包外面修正值,闭包内部的值也会改动。
Swift进阶(八)—— 闭包
经过lldb打印makeInc,咱们也能够看到,闭包里边也有Metadata

闭包的实质

这次咱们需要经过剖析IR文件来探究闭包的实质,首要咱们要了解一下IR语法

IR语法

i8Int8或许void *i16Int16i32Int32i64Int64void *

  • 数组
[<elementnumber> x <elementtype>]
//example
alloc [24 x i8], align 8 24i8都是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
}

Swift进阶(八)—— 闭包
从代码里边咱们能够了解到,在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

Swift进阶(八)—— 闭包
上面这个代码便是把指向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

现在咱们来验证这个成果

Swift进阶(八)—— 闭包
看成果和揣度的成果完全一致。

捕获引证类型

假如闭包捕获的变量类型是一个引证类型,比如一个类的实例目标,那又是怎样捕获的呢?首要咱们举一个比如

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
}

Swift进阶(八)—— 闭包

从这儿能够看出,在捕获引证类型时分,其实也不需要捕获实例目标,由于它现已在堆区了,就不需要再去创立一个堆空间的实例了,只需要将它的地址存储到闭包的结构中,操作实例目标的引证计数,就能够了。

接下来咱们运用上面闭包的结构验证一下

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()

打印成果如下:

Swift进阶(八)—— 闭包
闭包存储的堆空间地址,便是捕获到的引证类型实例变量的地址。

闭包捕获多个变量

咱们上面复原的是闭包捕获一个变量的状况,那假如闭包捕获多个变量,又是什么样的呢?咱们继续剖析两个变量的状况,代码如下:

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

这上面的代码别离是给捕获的变量amountrunningTotal别离创立了一个内存空间,并放在了%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进阶(八)—— 闭包
能够看到,和捕获两个变量的代码差不多相同,不同的是结构体变成了{ %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来明确闭包是允许逃逸的。

运用逃逸闭包一般满足以下两个条件:

  • 当闭包被当作特点存储,导致函数完结时闭包生命周期被延伸
    Swift进阶(八)—— 闭包
  • 当闭包异步履行,导致函数完结时闭包生命周期被延伸。
    Swift进阶(八)—— 闭包
  • 可选类型的闭包默许是逃逸闭包。
    Swift进阶(八)—— 闭包

以下这种闭包其实也是逃逸,关于编译器来说,把一个闭包赋值给了一个变量,编译器认为这个闭包可能会在其他地方去履行。

Swift进阶(八)—— 闭包

所以,逃逸闭包所需的条件:

  • 作为函数的参数传递。
  • 当时闭包在函数内部异步履行或许被存储。
  • 函数完毕,闭包被调用,闭包的生命周期未完毕。

主动闭包

@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