Swift. 闭包

563 阅读3分钟

闭包

闭包是一个捕获了上下文常量或变量的函数

定义了一个全局函数,这个全局函数也是一个闭包,只不过不捕获值

func test(){
    print("test")
}

内嵌函数,incrementer是一个闭包函数,并且捕获了makeIncrementer的变量runningTotal

func makeIncrementer() -> () -> Int{
    var runningTotal = 10
    //内嵌函数  是一个闭包
    func incrementer() -> Int{
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

闭包表达式

param参数,ReturnType返回值类型 in后面跟方法体
{ (param) -> ReturnType in
    //方法体
}

也可以把闭包表达式声明成一个可选类型

var clourse: ((Int) -> Int)?
clourse = nil

通过let把闭包声明成一个常量,闭包表达式赋值给了clourse这个常量,经过赋值后就不能更改

let clourse: (Int) -> Int
clourse = {(age: Int) in
    return age
}

把闭包当作一个参数传给函数

func test(param: () -> Int) {
    print(param())
}

var age = 10

test {() -> Int in
    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, item2, item3) -> Bool in
    return (item1 + item2 < item3)
}

捕获一个变量

如图,每次打印都会自增1,是因为在函数makeIncrementer中,变量runningTotal被闭包incrementer给捕获了,所以每次调用常量makeInc的时候会自增1,下面直接打印makeIncrementer()()却只会自增一次,查看sil文件 捕获值的本质是,在堆空间上开辟内存把值放入

闭包是引用类型

认识IR

IR的基本语法

IR中数组的数组
elementnumber数组中元素的个数,elementtype元素的类型,iN是表示多少位的整型,i8就是8位的整型就是1字节
[<elementnumber> x <elementtype>]
//example
alloca[24xi8],align8 24 i8 0

结构体
T是结构体名称,<type list>是结构体的成员列表
%swift.refcounted = type { %swift.type*, i64 }
%T = type {<type list>}

指针类型
<type> *
64位的整型,8字节
i64*

getelementptr指令
在LLVM中获取数组和结构体的成员时通过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;  // 假设P是有3个元素的数组。就可以直接通过下标读取
}
 
struct munger_struct array[3];

int main(int argc, const char * argv[]) {
    munge(array); //调用
    return 0;
}

通过如下命令生成IR文件
swiftc -emit-ir main.swift > main.ll

分析闭包

func makeIncrementer() -> () -> Int{
    var runningTotal = 10
    func incrementer() -> Int{
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}
let makeInc = makeIncrementer()

继续分析闭包,查看IR文件分析 根据上面的分析,来仿写一个把0x0000000100001920还原符号查看 捕获两个值 ir文件 仿写 通过0x00000001000058c0把函数复原 输出打印的地址由上图可知,在captureValue中是传入的捕获值类型,具体的类似Box结构体,只不过在Box结构体的成员上又增加了一个,输出后还是一个类似Box的结构体,修改Box结构体 捕获值的原理:堆上开辟内存空间,捕获的值放到这个内存空间里; 修改捕获值是是修改的这个内存空间里的值; 闭包是引用类型(地址传递)

逃逸闭包

逃逸闭包有两种情况:闭包被外部属性所持有,延迟调用闭包,都会使闭包的生命周期超过函数

自动闭包

添加@autoclosure关键字,可传函数类型,也可传函数的返回值类型,自动闭包不接受任何的参数