一、函数类型

Swift 中函数本身也有自己的类型,它由方式参数类型,回来类型组成。在运用函数作为变量时,假如有同名函数不指定类型会报错。

iOS-Swift 独孤九剑:七、闭包的本质

那么函数类型的实质是什么呢,咱们翻开源码,在 Metadata.h 文件中找到 TargetFunctionTypeMetadata:

iOS-Swift 独孤九剑:七、闭包的本质

留意看,TargetFunctionTypeMetadata 承继自 TargetMetadata,那么它必定有 Kind,而它自身又具有 Flags 和 ResultType,ResultType 是回来值类型的元数据。接下来咱们看到 getParameters 函数,这个函数经过 reinterpret_cast 将 (this + 1) 强制转化成 Parameter * 类型,留意!它回来的是指针类型。所以这个函数回来的是一块接连的内存空间,这一块接连的内存空间存储的是 Parameter 类型的数据。

关于 (this + 1) 能够参阅《元类型以及 Mirror 源码和 HandyJson 剖析复原枚举、结构体、类的 Metadata》这篇文章的第五点。那么 TargetFunctionTypeMetadata 的结构复原如下:

struct TargetFunctionTypeMetadata {
    var Kind: Int
    var Flags: Int
    var ResultType: Any.Type
    var parameters: ParametersBuffer<Any.Type>
}
struct ParametersBuffer<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))
        }
    }
}

TargetFunctionTypeMetadata 的结构复原出来了之后,咱们想获取参数的个数,在源码中是这么获取的:

iOS-Swift 独孤九剑:七、闭包的本质

Flags 的类型是 TargetFunctionTypeFlags,TargetFunctionTypeFlags 的结构如下:

iOS-Swift 独孤九剑:七、闭包的本质

它的 getNumParameters 办法如下:

iOS-Swift 独孤九剑:七、闭包的本质

TargetFunctionTypeFlags 存储的是一些掩码信息,经过阅读这个源码,得知 Data & NumParametersMask 得到的便是参数的个数,那么这个 Data 不便是 Flags 么,由于 TargetFunctionTypeFlags 只有 Data 一个成员变量。

此时,将 getNumParameters 函数复原出来的成果如下:

func getNumParameters() -> Int { self.Flags & 0x0000FFFF }

接下来咱们试着打印 TargetFunctionTypeMetadata 存储的信息,代码如下:

func add(_ a: Double, _ b: Int) -> Double {
    return a + Double(b)
}
let functionType = unsafeBitCast(type(of: add) as Any.Type, to: UnsafeMutablePointer<TargetFunctionTypeMetadata>.self)
let numParameters = functionType.pointee.getNumParameters()
print("函数参数的个数:\(numParameters)")
for i in 0..<numParameters {
    print("第\(i)个参数的类型:\(functionType.pointee.parameters.index(of: i).pointee)")
}
print("函数参数的回来值类型:\(functionType.pointee.ResultType)")
打印成果:
函数参数的个数:20个参数的类型:Double1个参数的类型:Int
函数参数的回来值类型:Double

二、闭包表达式

在 Swift 中,能够经过 func 界说一个函数,也能够经过闭包表达式界说一个函数。

1. 闭包表达式的书写

闭包表达式是由花括号、参数列表、回来值类型、in 以及函数体构成的,其书写如下:

{
    (参数列表) -> 回来值类型 in
    函数体代码
}

经过 {} 的办法开始,然后 in 前面的分别是参数列表和回来值类型,in 的后边是函数体代码。

闭包表达式界说的函数如下:

var add = {
    (a: Int, b: Int) -> Int in
    return a + b
}

func 界说的函数如下:

func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

不管那种办法,在调用的时分都是相同的,成果也是相同的。

print(add(10, 20)) // 30

2. 闭包表达式的简写

咱们界说一个函数 – exec,这个函数接纳三个参数,第一个参数和第二个参数的类型为 Int 类型,第三个参数的类型为函数类型。这个函数类型传两个 Int 类型的参数,回来 Int 类型的参数。

func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}

以下是这个函数的几种调用办法:

// 第一种
exec(v1: 10, v2: 20, fn: { (v1: Int, v2: Int) -> Int in
    return v1 + v2
})
// 第二种
exec(v1: 10, v2: 20, fn: { v1, v2 in
    return v1 + v2
})
// 第三种
exec(v1: 10, v2: 20, fn: { v1, v2 in
    v1 + v2
})
// 第四种
exec(v1: 10, v2: 20, fn: { $0 + $1 })
// 第五种
exec(v1: 10, v2: 20, fn: +)
  • 第一种写法没有任何的简写,闭包表达式的参数名、参数类型、回来值类型和函数体悉数写上。

  • 第二种写法相关于第一种写法省掉了参数类型和回来值类型。

  • 第三种写法相关于第二种写法省掉了 return,在 Swift 中,假如函数中回来值的代码很简单的话能够省掉 return(便是在函数体中只有一句 return 的代码)。

  • 第四种写法相关于第三种写法省掉了参数名和 in,由于函数体比较简单,所以能够直接用 0和0 和 1 分别代表 v1 和 v2。

  • 第五种写法相关于第四种写法省掉了函数体,由于函数体的完结过于简单,仅仅两个参数相加,所以能够直接用 + 表示。

3. 跟随闭包

假如将一个很长的闭包表达式作为函数的最终一个实参,运用跟随闭包能够增强函数的可读性。跟随闭包是一个被书写在函数调用括号外面(后边)的闭包表达式。 例如第 3 点的 exec 函数,调用的时分,能够运用跟随闭包:

exec(v1: 10, v2: 20) { v1, v2 in
    return v1 + v2
}

假如闭包表达式是函数的仅有实参,而且运用了跟随闭包的语法,那就不需要在函数后边加圆括号。

func exec(fn: (Int, Int) -> Int) {
    print(fn(10, 20))
}
exec(fn: {$0 + $1})
exec() {$0 + $1}
exec {$0 + $1}

三、闭包

一个函数和它所捕获的变量\常量环境组合起来,称为闭包。一般指界说在函数内部的函数,它捕获的是外层函数的局部变量\常量。

1. 捕获局部变量

如下代码,咱们用 typealias 界说一个函数类型 Fn,再界说一个 getFn 的函数,如下:

iOS-Swift 独孤九剑:七、闭包的本质

图中红色框框的,组合起来称之为闭包。num 是一个局部变量,调用 getFn 函数来看一下它的变化:

let fn = getFn()
print(fn(1))   // 1
print(fn(1))   // 2
print(fn(1))   // 3

每次调用 fn,传的值都为 1,但是每次打印都是不相同的,感觉就像是 num 这个局部变量被放到堆空间存储起来了,在每次调用的时分都是 num += 1。咱们经过汇编来看一下 getFn 的调用状况。

iOS-Swift 独孤九剑:七、闭包的本质

留意看,在 getFn 的汇编中,调用了 swift_allocObject 办法,这个办法在干什么,在申请并分配堆空间的内存。所以实践上闭包会拓荒堆空间的内存,把 num 的值放到堆空间上,当每次调用 fn 的时分,都会去堆空间访问这个值,然后进行 += 的操作。

怎样知道这个 num 值在堆空间呢,在汇编调试中的 swift_allocObject 办法后边打下一个断点,然后读取拓荒堆空间的内存,如图:

iOS-Swift 独孤九剑:七、闭包的本质

接下来咱们将断点打在 return num 处,然后将断点过掉,格式化输出 0x0000000101019d10,如图:

iOS-Swift 独孤九剑:七、闭包的本质

iOS-Swift 独孤九剑:七、闭包的本质

格式化输出堆空间的内存后发现,num 的值存储在 0x101019d20 这个内存地址,它的确存储在堆空间。

2. 捕获大局变量

当函数捕获一个局部变量/常量时,会拓荒堆空间的内存去存储这个局部变量/常量,那假如捕获的是一个大局变量呢,会拓荒堆空间吗。代码如下:

iOS-Swift 独孤九剑:七、闭包的本质

咱们将断点打在 return plus 和 return num 处,先来来看一下 getFn 在汇编的调用状况。

iOS-Swift 独孤九剑:七、闭包的本质

能够看到,在 getFn 的汇编里并没有产生任何的堆空间拓荒,它是直接将 plus 函数的地址回来出去,咱们接下来再来看一下 plus 函数的汇编代码:

iOS-Swift 独孤九剑:七、闭包的本质

留意看,在 plus 函数中,它是直接拿到大局变量 num 直接修改的。所以函数不会去捕获大局变量/常量,因而这种行为严格上也不叫做闭包。

四、闭包的实质

在探究闭包的实质的时分,需要借助 IR 的代码进行剖析,所以咱们先来了解一下 IR 的部分语法。

1. IR 的语法

数组

[<elementnumber> x <elementtype>]
//example
alloca [24 x i8], align 8 24个i8都是0
alloca [4 x i32] === array

结构体:

%swift.refcounted = type { %swift.type*, i64 }
//表示方式
%T = type {<type list>} //这种和C言语的结构体相似

指针类型:

<type> *
//example
i64* //64位的整形

getelementptr 指令:

LLVM中咱们获取数组和结构体的成员,经过 getelementptr ,语法规则如下:

<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx

这儿咱们看 LLVM 官网傍边的一个例子:

struct munger_struct {
    int f1;
    int f2;
};
void munge(struct munger_struct *P) {
    P[0].f1 = P[1].f1 + P[2].f2;
}
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i64
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i32
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, i32 0

总结如下:

  • 第一个索引不会改变回来的指针的类型,即ptrval前面对应什么类型,回来的便是什么类型。

  • 第一个索引的偏移量是由第一个索引的值和第一个ty指定的根本类型一起确定的。

  • 第二个索引是在数组或许结构体内进行索引,内部偏移多少元素巨细。

  • 每添加一个索引,就会使得该索引运用的根本类型和回来的指针类型去掉一层。

例如获取 [4 x i32] 数组地址中第一个所有去除的类型是 [4 x i32] 第二个索引获取的类型是 i32。

iOS-Swift 独孤九剑:七、闭包的本质

2. IR 剖析闭包

代码如下:

typealias Fn = (Int) -> Int
func getFn() -> Fn {
    var num = 0
    func plus(_ i: Int) -> Int {
        num += i
        return num
    }
    return plus
}
let fn = getFn()

2.1. main 函数剖析

咱们将当时的 main.swift 文件编译成 main.ll 文件,编译办法及的指令在这篇文章:《办法》,生成 main.ll 文件之后,咱们翻开,找到 main 函数,如下:

define i32 @main(i32 %0, i8** %1) #0 {
entry:
    %2 = bitcast i8** %1 to i8*
    // 这儿调用 main 函数中的 getFn 函数,它的回来值在 IR 中变成了 { i8*, %swift.refcounted* }。
    %3 = call swiftcc { i8*, %swift.refcounted* } @"main.getFn() -> (Swift.Int) -> Swift.Int"()
    %4 = extractvalue { i8*, %swift.refcounted* } %3, 0
    %5 = extractvalue { i8*, %swift.refcounted* } %3, 1
    store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"main.fn : (Swift.Int) -> Swift.Int", i32 0, i32 0), align 8
    store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"main.fn : (Swift.Int) -> Swift.Int", i32 0, i32 1), align 8
    ret i32 0
}

留意看!在 %3 这一行调用了 getFn 函数,而且它的回来值是一个 { i8*, %swift.refcounted* },大局查找这个回来值,查找的成果如下:

%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* }

依据 IR 的语法进行剖析:

  • { i8*, %swift.refcounted* } 是一个结构体,这个结构体包括两个成员变量,分别为 i8* 类型的成员和 %swift.refcounted* 类型的成员。

  • %swift.refcounted* 是一个结构体指针,它的结构为 { %swift.type*, i64 },这个结构体包括两个成员变量,分别为 %swift.type* 类型的成员和 i64 类型的成员。

  • %swift.type* 是一个结构体指针,它的结构为 { i64 },它只包括 i64 类型的成员变量。

  • %swift.full_boxmetadata 应该是一个独属于闭包的 metadata,在下面的拓荒堆空间的时分,swift_allocObject 传的便是这个玩意儿。

2.2. getFn 函数剖析

getFn 的函数完结如下:

define hidden swiftcc { i8*, %swift.refcounted* } @"main.getFn() -> (Swift.Int) -> Swift.Int"() #0 {
entry:
    %num.debug = alloca %TSi*, align 8
    %0 = bitcast %TSi** %num.debug to i8*
    call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
    // 调用 swift_allocObject,创立一个实例,依据第一篇的《结构体与类》,中得知,它回来的是一个 HeapObject * 的结构体指针。所以,%swift.refcounted* 应该是一个 HeapObject *。
    // swift_allocObject 的第一个参数要求传的是 metadata,那么 i64 24 和 i64 7 对应的应该是分配内存的巨细和内存对齐。
    %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_allocObject 回来的 HeapObject * 强制转化成 { %swift.refcounted, [8 x i8] }
    %2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
    // 将局部变量 num 的值存储到 { %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** %num.debug, align 8
    %._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
    store i64 0, i64* %._value, align 8
    %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
    call void @swift_release(%swift.refcounted* %1) #1
    // insertvalue 是刺进、存储值。
    // bitcast 是强制转化的意思,将 plus 函数的地址强制转化成 i8*,然后将这个值刺进到 { i8*, %swift.refcounted* } 这个结构体的 i8*。
    // 然后将上面的 { %swift.refcounted, [8 x i8] } 存储到 { i8*, %swift.refcounted* } 这个结构体的 %swift.refcounted* 这个位置。
    %6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (i64, %swift.refcounted*)* @"partial apply forwarder for plus #1 (Swift.Int) -> Swift.Int in main.getFn() -> (Swift.Int) -> Swift.Int" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
    // 将 { i8*, %swift.refcounted* } 结构回来。
    ret { i8*, %swift.refcounted* } %6
}

留意看 %1 这一行,调用 swift_allocObject,创立一个实例,依据第一篇的《结构体与类》,中得知,它回来的是一个 HeapObject * 的结构体指针。所以,%swift.refcounted* 应该是一个 HeapObject *。swift_allocObject 的第一个参数要求传的是 metadata,那么 i64 24 和 i64 7 对应的应该是分配内存的巨细和内存对齐。

中心的部分做了一些强制转化和赋值 swift_allocObject 回来的 HeapObject * 最终变成了 { %swift.refcounted, [8 x i8] } 这个结构。

看到 %6 这一行。这一行在干什么,在把 plus 函数的地址和 { %swift.refcounted, [8 x i8] } 存储到 { i8*, %swift.refcounted* } 这个结构体,最终将 { i8*, %swift.refcounted* } 回来。

3. 闭包的结构复原

经过以上剖析得知:

  • 闭包实质上便是一个 { i8*, %swift.refcounted* } 这样的结构,i8* 存储的是函数的地址,%swift.refcounted* 存储的是一个 box *({ %swift.refcounted, [8 x i8] })。

  • 而 box * 里有 HeapObject * 和一个 64 位的 value。

  • HeapObject * 咱们就比较了解,分别存储 metadata 和 refcount。

闭包最终的结构就能够复原出来了,复原出来的结构如下:

struct ClosureData<Box> {
    /// 函数地址
    var ptr: UnsafeRawPointer
    /// 存储捕获堆空间地址的值
    var object: UnsafePointer<Box>
}
struct Box<T>{
    var heapObject: HeapObject
    // 捕获变量/常量的值
    var value: T
}
struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount: Int
}

用代码验证一下:

//用结构体包裹一下闭包便当作指针的转化
struct ClosureStruct {
    var closure :(Int) -> Int
}
var fn = ClosureStruct(closure: getFn())
fn.closure(10)
//fn 初始化一个ClosureStruct类型指针
let ptr = UnsafeMutablePointer<ClosureStruct>.allocate(capacity: 1)
ptr.initialize(to: fn)
//内存从头绑定为 ClosureData<Box<Int>>
let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int>>.self, capacity: 1){
    $0.pointee
}
print("闭包的调用地址:",ctx.ptr)
print("堆空间地址:",ctx.object)
print("堆空间存储的值", ctx.object.pointee.value)
ptr.deinitialize(count: 1)
ptr.deallocate()

为了验证打印出的函数地址是否便是 plus 函数的地址,咱们能够在 return plus 处打一个断点,然后经过汇编来读取 plus 函数的地址,如图:

iOS-Swift 独孤九剑:七、闭包的本质

能够看到 plus 函数的地址为 0x0000000100002c00,咱们铺开断点,持续履行,测验代码打印出的函数地址和堆空间的地址,如图:

iOS-Swift 独孤九剑:七、闭包的本质

plus 函数的地址验证成功,捕获的变量的值也存储在堆空间,而且,这个值在 HeapObject 之后,所以咱们复原出来的结构是正确的。

4. 捕获引证类型

捕获一个值类型,会在堆空间拓荒内存,那么捕获引证类型呢,咱们经过汇编来剖析,代码如下:

typealias Fn = (Int) -> Int
class SHPerson {
    var age = 0
}
func getFn() -> Fn {
    let person = SHPerson()
    func plus(_ i: Int) -> Int {
        person.age += i
        return person.age
    }
    return plus
}
let fn = getFn()

咱们在 return plus 和 person.age += i 处打一个断点,先来看一下 getFn 函数的汇编完结状况:

iOS-Swift 独孤九剑:七、闭包的本质

能够看到,仅有拓荒的堆空间的无非便是 SHPerson 的初始化,那么它捕获的是什么呢,咱们经过读取 rax 的值:0x000000010126c330,这个值便是 person 的内存地址。接下来铺开断点,用咱们的测验代码将闭包的结构打印出来,结构如下:

iOS-Swift 独孤九剑:七、闭包的本质

能够看到,ClosureData 中 object 存储的地址直接便是 person 内存地址。由于在初始化 SHPerson 的时分已经拓荒了堆空间,没有必要再拓荒一个堆空间来捕获这个 person,所以直接把 person 的内存地址直接放到 ClosureData 中,这样能够防止不必要的内存开销。

5. 捕获多个值

5.1. 剖析 getFn 函数

假如捕获多个值,闭包的结构还和第 3 点复原出来的相同吗,代码如下:

typealias Fn = (Int) -> Int
func getFn() -> Fn {
    var num1 = 0
    var num2 = 0
    func plus(_ i: Int) -> Int {
        num1 += i
        num2 += (num1 + 1)
        return num2
    }
    return plus
}
let fn = getFn()

咱们将当时的 main.swift 文件编译成 main.ll 文件,编译成功之后咱们直接看 getFn 函数的完结:

iOS-Swift 独孤九剑:七、闭包的本质

代码比较长,我只截了要害的部分。能够看到,在捕获多个值后,相对应的也多次调用了 swift_allocObject 办法。留意看,第一次和第2次调用 swift_allocObject 都是为了存储 num1 和 num2 的值。

比较有意思的是第三次调用 swift_allocObject,回来的实例被强制转化成了一个结构体指针:

<{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }>*

留意看 getelementptr,第三次调用 swift_allocObject 后有两次 getelementptr。这两次的 getelementptr 在干什么,在把前两次 swift_allocObject 的结构体存储到 %13 这个结构体。

5.2. 复原闭包的结构

依据以上剖析,咱们将这个闭包的结构复原出来后如下:

struct ClosureData<MutiValue>{
    /// 函数地址
    var ptr: UnsafeRawPointer
    /// 存储捕获堆空间地址的值
    var object: UnsafePointer<MutiValue>
}
struct MutiValue<T1,T2>{
    var object: HeapObject
    var value:  UnsafePointer<Box<T1>>
    var value1:  UnsafePointer<Box<T2>>
}
struct Box<T>{
    var object: HeapObject
    var value: T
}
struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount: Int
}

测验代码如下:

//用结构体包裹一下闭包便当作指针的转化
struct ClosureStruct {
    var closure :(Int) -> Int
}
var fn = ClosureStruct(closure: getFn())
fn.closure(10)
//fn 初始化一个ClosureStruct类型指针
let ptr = UnsafeMutablePointer<ClosureStruct>.allocate(capacity: 1)
ptr.initialize(to: fn)
//内存从头绑定为 ClosureData<Box<Int>>
let ctx = ptr.withMemoryRebound(to: ClosureData<MutiValue<Int, Int>>.self, capacity: 1){
    $0.pointee
}
print("闭包的调用地址:",ctx.ptr)
print("堆空间地址:",ctx.object)
print("堆空间存储的值", ctx.object.pointee.value.pointee.value, ctx.object.pointee.value1.pointee.value)
ptr.deinitialize(count: 1)
ptr.deallocate()
打印成果:
闭包的调用地址: 0x0000000100002840
堆空间地址: 0x000000010111f400
堆空间存储的值 10 11

5.3.捕获单个值和多个值的闭包结构总结

依据以上的剖析,捕获单个值和多个值的区别就在于:

  • 单个值中,ClosureData 内存储的堆空间地址直接便是这个值所在的堆空间。

  • 而关于捕获多个值,ClosureData 内存储的堆空间地址会变成一个能够存储很多个捕获值的结构。

总的来时捕获多个值比捕获单个值多了一层包装,那么总结的代码结构如下:

// 捕获单个值的 ClosureData
struct ClosureData<Box> {
    /// 函数地址
    var ptr: UnsafeRawPointer
    /// 存储捕获堆空间地址的值
    var object: UnsafePointer<Box>
}
// 捕获多个值的 ClosureData
struct ClosureData<MutiValue>{
    /// 函数地址
    var ptr: UnsafeRawPointer
    /// 存储捕获堆空间地址的值
    var object: UnsafePointer<MutiValue>
}
struct MutiValue<T1, T2, ......>{
    var object: HeapObject
    var value:  UnsafePointer<Box<T1>>
    var value1:  UnsafePointer<Box<T2>>
    // 更多的 value
    ......
}
struct Box<T>{
    var object: HeapObject
    var value: T
}
struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount: Int
}

五、逃逸闭包

当闭包作为一个实践参数传递给一个函数的时分,而且是在函数回来之后调用,咱们就说这个闭包逃逸了。当咱们声明一个接受闭包作为方式参数的函数时,能够在方式参数前写 @escaping 来清晰闭包是答应逃逸的。

  • 当闭包被当作属性存储,导致函数完结时闭包生命周期被延伸。

iOS-Swift 独孤九剑:七、闭包的本质

  • 当闭包异步履行,导致函数完结时闭包生命周期被延伸。

    iOS-Swift 独孤九剑:七、闭包的本质

  • 可选类型的闭包默许是逃逸闭包。

    iOS-Swift 独孤九剑:七、闭包的本质

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

func test() -> Int{
    var age = 10
    let completeHandler = {
        age += 10
    }
    completeHandler()
    return age
}

逃逸闭包所需的条件:

  • 作为函数的参数传递。

  • 当时闭包在函数内部异步履行或许被存储。

  • 函数完毕,闭包被调用,闭包的生命周期未完毕。

六、主动闭包

@autoclosure 是一种主动创立的闭包,用于将参数包装成闭包。这种闭包不接受任何参数,当它被调用的时分,会回来传入的值。这种便当语法让你在调用的时分能够省掉闭包的花括号。

什么意思呢,咱们来看下面的代码:

// 假如第1个数大于0,回来第一个数。不然回来第2个数
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
    return v1 > 0 ? v1 : v2
}
print(getFirstPositive(10, 20))    // 10
print(getFirstPositive(-2, 20))    // 20
print(getFirstPositive(0, -4))     // -4

用了一个三目运算符,判别回来的是 v1 还是 v2,接下来我添加一个测验函数,如下:

func getNum() -> Int {
    print("getNum")
    let a = 10
    let b = 20
    return a + b
}
print(getFirstPositive(10, getNum()))
打印成果:
getNum
10

留意看,我传一个 10 给 getFirstPositive 办法,的确也回来一个 10 了,但是却打印出了 getNum,但其实判别 v1 > 0 并不需要调用 getNum 函数,但编译器还是履行了。

这个时分咱们能够把 v2 变成一个函数,也便是咱们传一个函数进去,代码如下:

func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
print(getFirstPositive(10, {
        print("test")
        return 30 }))
打印成果:
10

能够看到,调用 getFirstPositive 后并没有打印出 test,这就能够去优化咱们的代码,防止不必要的代码履行。但是假如咱们的代码比较简单,就像上面的例子,能够这么写:

print(getFirstPositive(10, {20}))

但是这种写法就比较麻烦,每次都要加上一对花括号,那咱们就能够用主动闭包去表达,如何运用主动闭包呢,代码如下:

func getFirstPositive(_ v1: Int, _ v2:@autoclosure () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
print(getFirstPositive(10, 20))

在 v2: 后边加上了 @autoclosure 就形成了主动闭包,咱们在运用的时分也能够省掉掉花括号。

  • @autoclosure 只支撑 () -> T 格式的参数。

  • @autoclosure 并非只支撑最终一个参数。

  • 空兼并运算符(??)运用了 @autoclosure 技术。

  • 有 @autoclosure,无 @autoclosure 构成函数重载。