程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长

+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

10、Swift闭包

发布于2021-06-07 20:30     阅读(1151)     评论(0)     点赞(23)     收藏(5)


一、闭包是⼀个捕获了上下⽂的常量或者是变量的函数。

  1. func testFunc()  {
  2.     print("test method")
  3. }
  • 上⾯的函数是⼀个全局函数,也是⼀种特殊的闭包,只不过当前的全局函数并不捕获值。
  • 下面我们查看一个有内嵌函数的闭包
  1. func makeIncrementer() -> () -> Int {
  2.     var runningTotal = 10
  3.     func incrementer() -> Int{
  4.         runningTotal += 1
  5.         return runningTotal
  6.     }
  7.     return incrementer
  8. }
  • 上⾯的 incrementer() 我们称之为内嵌函数,同时从上层函数 makeIncrementer() 中捕获变量 runningTotal (下面会有探索过程)。
  • 下面我们查看一个闭包表达式
  1. var t = { (age:Int) in
  2.         return age
  3.         }
  4. let b = t(20)
  5. print(b) // 20
  • { } 作用域括起来的这些就是我们的闭包表达式、是一个匿名函数,而且从上下文中捕获变量和常量。
  • 使用闭包表达式能更简介的传达信息。并且闭包表达式的优点有很多:
    • 利用上下文推断参数和返回值类型
    • 单表达式可以隐士返回,即省略return关键字
    • 参数名称的简写(比如我们常用的 $0)
    • 尾随闭包表达式
  • 下面我们由浅入深、开始探索闭包
    • 回顾一下闭包表达式的定义、直接写出 closure即有提示而出Closure Expression
  1. { (parameters) -> return type in
  2.     statements
  3. }
  4. // 此处参数为Int类型 -> 返回值类型也为Int
  5. var a = { (param:Int) -> Int in
  6.     return param+1
  7. }
    • ⾸先按照我们之前的知识积累, OC 中的 Block 其实是⼀个匿名函数,所以这个表达式要具备
    • 作用域(也就是大括号)
    • 参数和返回值 (param、returnType)
  • 函数体 ( in 之后的代码)我们常常把我们的闭包声明为一个可选类型。
  1. var closure:((Int) -> Int)?
  2. closure = { (param:Int) -> Int in
  3.     return param+1
  4. }
  5. print(closure?(10) ?? 0)//11
  6. closure = nil //可置空
  7. //由于当前的闭包表达式被定义为变量、所以赋值之后还可以再次更改。如果为let则不可再次更改
  8. closure =  { (param:Int) -> Int in
  9.     return param * param
  10. }
  11. print(closure?(10) ?? 0)//100
    • 与此同时、闭包还可以作为函数的参数
  1. func closureTest(param:() -> Int){
  2.     print(param()) //25
  3. }
  4. var age:Int = 20
  5. closureTest { () -> Int in //尾随闭包
  6.     age+=5
  7.     return age
  8. }
  • 上例是一个尾随闭包的写法、下面我们介入尾随闭包的概念

二、尾随闭包:

  • 当我们把闭包表达式作为函数的最后⼀个参数,如果当前的闭包表达式很⻓,我们可以通过尾随闭包的书写⽅式来提⾼代码的可读性。
    • 怎么玩尾随闭包呢?下面我们看一个案例
  1. func closureAfter(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool{
  2.     return by(a, b, c)
  3. }
    • 上述函数中
    • 有 a、b、c、by 四个参数、
    • 而by的参数又是一个由item1 、item2、item3三个参数及Bool返回值组成的闭包
    • 最后函数整体返回值为 Bool类型
  • 怎么调用呢?下面我们开始玩转调用
    • 最最最常规的写法为下面的写法、完全不省略任何参数
  1. closureAfter(10, 20, 30,by:{ (_ item1:Int, _ item2:Int, _ item3:Int) -> Bool in
  2.     return item1 + item2 < item3
  3. })
      • 我们在查找方法调用时:一般以小括号开始、小括号结束。
  • 所以假设我们的参数非常多、那么我们在查找一个函数调用的时候就非常费劲、尤其是在代码量多的时候
    • 因此我们可以通过闭包表达式中尾随闭包的写法、将当前参数函数的调用写在()外边。
  1. closureAfter(10, 20, 30){ (_ item1:Int, _ item2:Int, _ item3:Int) -> Bool in
  2.     return item1 + item2 < item3
  3. }
    • 这⾥⼀眼看上去就知道是⼀个函数调⽤,后⾯是⼀个闭包表达式。当前闭包表达式{} 放在了函数外⾯ 。
  • 下面我们继续对其简化、

1、省略参数类型

  1. closureAfter(10, 20, 30) { (item1, item2, item3) -> Bool in
  2.     return item1 + item2 < item3
  3. }

2、省略by参数闭包的返回值

  1. closureAfter(10, 20, 30) { (item1, item2, item3) in
  2.     return item1 + item2 < item3
  3. }

3、省略 return 关键字

  1. closureAfter(10, 20, 30) { (item1, item2, item3)in
  2. item1 + item2 < item3
  3. }

4、省略参数名称及 in、使用 $0、$1、$2来表示参数

closureAfter(10, 20, 30) { return $0 + $1 < $2 }

5、继续省略 return关键字

closureAfter(10, 20, 30) { $0 + $1 < $2}
    • 因为有三个参数、所以我们不能继续再省略了、然而查看Array的玩法、array.sorted的尾随闭包可以用最简的一个 < 来返回闭包值:
  1. var array = [1,2,4,3,0]
  2. array = array.sorted(by: <)
  3. print(array) //[0, 1, 2, 3, 4]

三、闭包的捕获值

    • 关于捕获值、我们回到官方文档中的例子来做一个具体说明
  1. func makeIncrementer() -> () -> Int {
  2.     var runningTotal = 10
  3.     func incrementer() -> Int{
  4.         runningTotal += 1
  5.         return runningTotal
  6.     }
  7.     return incrementer
  8. }
  9. let makeInc = makeIncrementer()//将返回函数传给常量 makeInc
  10. print(makeInc()) //11
  11. print(makeInc()) //12
  12. print(makeInc()) //13
  13. //然而直接调用三次的结果却为
  14. print(makeIncrementer()()) //11
  15. print(makeIncrementer()()) //11
  16. print(makeIncrementer()()) //11

结果为什么不一样呢?这里我们引入捕获值

      • 当我们直接调用函数 makeIncrementer()() 时我们直接返回的是当前临时变量 runningTotal + 1 后的结果;理论上来说、这样才是该函数的结果、每次都应该是11
      • 然而我们在直接使用 makeInc 去调用时、意味着我们的内嵌函数 incrementer() 捕获了我们的变量 runningTotal、意味着这个变量已经不是单纯的变量了、那它到底是什么呢?我们通过SIL来查看一下makeIncrementer函数内部实现
  1. // makeIncrementer()
  2. sil hidden @main.makeIncrementer() -> () -> Swift.Int : $@convention(thin) () -> @owned @callee_guaranteed () -> Int {
  3. bb0: //alloc_box 创建一个变量给我们当前的变量 runningTotal、相当于把一个引用地址给了我们的 runningTotal,意味着当前的变量放到了我们当前的堆上
  4. %0 = alloc_box ${ var Int }, var, name "runningTotal" // users: %8, %7, %6, %1
  5. //project_boc取出创建好的这个变量
  6. %1 = project_box %0 : ${ var Int }, 0 // user: %4
  7. %2 = integer_literal $Builtin.Int64, 10 // user: %3
  8. %3 = struct $Int (%2 : $Builtin.Int64) // user: %4
  9. store %3 to %1 : $*Int // id: %4
  10. // function_ref incrementer #1 () in makeIncrementer()
  11. //在调用过程中、这个创建好的变量就传递给了我们的闭包来使用
  12. %5 = function_ref @incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %7
  13. //闭包调用开始、对这个对象做了强引用计数+1操作
  14. strong_retain %0 : ${ var Int } // id: %6
  15. //闭包调用过程
  16. %7 = partial_apply [callee_guaranteed] %5(%0) : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %9
  17. //对这个对象做了强引用计数的-1操作
  18. strong_release %0 : ${ var Int } // id: %8
  19. return %7 : $@callee_guaranteed () -> Int // id: %9
  20. } // end sil function 'main.makeIncrementer() -> () -> Swift.Int'
      • 其中的 alloc_box代表什么意思呢?

  • alloc_box: 在堆上分配一块内存空间,存储了metadata,refCount,当前的 value。
    • 所以我们捕获值的本质就是:在堆上开辟一块空间、把我们的变量放到其中。当前闭包是内嵌函数+捕获上下文的变量/常量;我们可以通过汇编调用来验证

综上总结:

      • ⼀个闭包能够从上下⽂捕获已被定义的常量和变量。即使定义这些常量和变量的原作⽤域已经不存在, 闭包仍能够在其函数体内引⽤和修改这些值。
      • 当我们每次修改的捕获值的时候,修改的是堆区中的 value 值
      • 当每次重新执⾏当前函数时候,都会重新创建内存空间

四、闭包是引用类型

  • 上⾯的代码中,我们把⼀个函数 makeIncrementer() 赋值给了⼀个变量 makeInc,那么这个时候变量makeInc ⾥⾯存储的是什么?是函数地址吗?下面我们对其进行探索
  1. func makeIncrementer() -> () -> Int {
  2.     var runningTotal = 10
  3.     func incrementer() -> Int{
  4.         runningTotal += 1
  5.         return runningTotal
  6.     }
  7.     return incrementer
  8. }
  9. let makeInc = makeIncrementer()
  10. print(makeInc())
    • 通过lldb的打印、我们看不出来makeInc的具体内容
  1. (lldb) po makeInc
  2. (Function)
    • 通过SIL我们也没有看出他的具体内容

  • 这个时候我们把我们的 SIL再降一级,通过IR来观察数据的构成
  • 要通过LLVM IR看具体的内容时,需要先去LLVM官网去熟悉一下基本的IR语法
  • 整型:为所需的整数类型指定任意位宽。可以指定从1位到2的23次方-1(约800万)的任何位宽度
  1. iN //N位的整型值
  2. i1 //一位整数。
  3. i8 //一个8位的整型:也就是1字节
  4. i32 //一个32位整数:4字节
  • 浮点型
  1. half //16位浮点值
  2. float //32位浮点值
  3. double //64位浮点值
  4. fp128 //128位浮点值
  • 数组:数组类型是一种非常简单的派生类型,可以将元素顺序地排列在内存中。数组类型需要大小(元素数)和基础数据类型。
  1. [<# elements> x <elementtype>] //elements是一个恒定的整数值;elementtype可以是任何大小的类型。
  2. alloca [24 x i8],algin 8 //24个8位整型值都是0的数组
  3. [40 x i32] //40个32位整型值的数组。
  4. [4 x i8] //4个8位整型值的数组。
  5. [3 x [4 x i32]] //3x4的32位整型值数组。
  6. [12 x [10 x float]] //12x10的单精度浮点值数组。
  • 结构体:结构体类型用于表示内存中数据成员的集合。结构体的元素可以是具有大小的任何类型。使用“getelementptr”指令获取指向元素的地址,从而使用“load”和“store”访问地址而获得内存中的结构体。使用'extractvalue'和'insertvalue'指令访问寄存器中的结构体。
  1. %T1 = type { <type list> } ; //正常结构体类型
  2. %T2 = type <{ <type list> }> ; //封装结构体类型
  3. //swift.refcounted结构体: 第一个元素为 swift.type类型的指针,第二位为64位整型值(8字节)
  4. %swift.refcounted = type { %swift.type*,i64 }
  5. { i32, i32, i32 } //三组32位整形值
  6. { float, i32 (i32) * } //第一个元素为浮点型,第二个元素是一个指向函数的指针:该函数参数为32位整型值,返回32位整型值
  7. <{ i8, i32 }> //被定义为一个5字节大小的封装结构体
  • 指针:指针类型用于指定存储位置。指针通常用于引用内存中的对象。指针类型可能具有可选的地址空间属性,该属性定义了指向对象所驻留的编号地址空间。默认地址空间为数字零。非零地址空间的语义是特定于目标的。LLVM不允许(void*)空指针,也不允许(label*)类型指针:使用 i8* 代替。
  1. <type> *
  2. i64* //指向 i64类型 的指针
  3. [4 x i32]* //指向 4个i32类型的数组 的指针
  4. i32 (i32*) * //指向 参数为i32、返回值为i32的函数 的指针
  5. i32 addrspace(5)* //指向 在内存地址5中的i32类型 的指针
  • getelementptr指令:LLVM中我们获取数组和结构体的成员,通过 getelementptr;它只执行地址计算,不访问内存。
  1. <result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
  2. <result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
  3. <result> = getelementptr <ty>, <ptr vector> <ptrval>, [inrange] <vector index type> <idx>
      • 第⼀个索引不会改变返回的指针的类型,也就是说ptrval前⾯的*对应什么类型,返回就是什么类型
      • 第⼀个索引的偏移量的是由第⼀个索引的值和第⼀个ty指定的基本类型共同确定的。
      • 后⾯的索引是在数组或者结构体内进⾏索引
      • 每增加⼀个索引,就会使得该索引使⽤的基本类型和返回的指针的类型去掉⼀层
    • 下面我们为了更好的理解LLVM IR,引入另一个案例:
  1. struct munger_struct {
  2.     int f1;
  3.     int f2;
  4. };
  5. void munge(struct munger_struct *p){
  6.     p[0].f1 = p[1].f1 + p[2].f2;
  7. }
  8. struct munger_struct array[3];
  9. //int main(int argc, const char * argv[]) {
  10. //    munge(array); //将结构体数组传递给函数
  11. //    return 0;
  12. //}
    • 通过clang生成 main.c 的 LLVM IR 代码指令
  1. clang main.c -emit-llvm -S -c -o main.ll //LLVM IR 生成指令
  2. clang StructIR/main.c -emit-llvm -S -c -o main.ll > ./main.ll && open main.ll //案例中生成并打开文件的指令
    • 下面我们来分析其生成的IR代码:
  1. //1、定义了一个struct.munger_struct 的结构体 = 第一个参数为 i32,第二个参数也为 i32
  2. %struct.munger_struct = type { i32, i32 }
  3. //2、自定义的全局数组:3个 struct.munger_struct结构体类型的数组、并初始化
  4. @array = common global [3 x %struct.munger_struct] zeroinitializer, align 16
  5. define void @munge(%struct.munger_struct* %0) #0 {
  6. //5、%2创建了一片内存空间、存放的是我们当前 struct.munger_struct结构体 的地址: 所以当前的%2是一个地址的地址(二级指针)
  7. %2 = alloca %struct.munger_struct*, align 8
  8. //由%struct.munger_struct** %2 可见 %2 二级指针
  9. store %struct.munger_struct* %0, %struct.munger_struct** %2, align 8
  10. // %3 = 当前数组的首地址
  11. %3 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  12. //8、访问当前的p[1] : 当前index = 1,1 x 结构体大小(4字节)、访问我们数组当中的第二个元素p[1]
  13. %4 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
  14. //9、访问数组中第二个结构体的第一个成员f1
  15. %5 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %4, i32 0, i32 0
  16. //14、 %6 = 访问 p[1].f1
  17. %6 = load i32, i32* %5, align 4
  18. //15、当前数组的首地址
  19. %7 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  20. //10、访问当前的p[2]: 当前的 index = 2, 2 x 结构体大小(4字节),访问我们数组中的第三个元素p[2]
  21. %8 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %7, i64 2
  22. //11、访问数组中的第三个结构体的第二个成员f2
  23. %9 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %8, i32 0, i32 1
  24. //16、 %10 = 访问 p[2].f2
  25. %10 = load i32, i32* %9, align 4
  26. //17、 %11 = p[1].f1+p[2].f2
  27. %11 = add nsw i32 %6, %10
  28. //4、 %12 访问的是我们的 %2。 6、取出%2的首地址给%12;此时%12存放的是当前数组的首地址
  29. %12 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  30. //3、 当前返回值类型位 %struct.munger_struct结构体类型、需要拿到数组的基地址,%struct.munger_struct* %12是当前索引结构体的地址、i64 0 当前数组的index;
  31. %13 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %12, i64 0 //12、访问当前数组的第一个元素p[0]
  32. //7、%13 是我们当前数组的第一个结构体元素;第一个i32 0 相当于结构体指针偏移0字节,也就是不偏移,第二个i32 0 表示第一个结构体成员
  33. %14 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %13, i32 0, i32 0 //13、访问数组中第一个结构体的第一个成员f1
  34. //18、 p[0].f1 = p[1].f1+p[2].f2
  35. store i32 %11, i32* %14, align 4
  36. ret void
  37. }
    • 代码中我们从左往右看、而底层程序理解过程中则是从右往左看,所以最后的 %12、%13、%14才是对我们的 p[0].f1进行赋值操作
      • 我们结合下面的案例和图片来对取数组首元素进行理解
  1. int main(int argc, const char * argv[]) {
  2. int array[4] = {1, 2, 3, 4};
  3. int a = array[0];
  4. return 0;
  5. }
  6. //其中 int a = array[0] 这句对应的LLVM代码应该是这样的:
  7. %6 = alloca [4 x i32], align 16
  8. %9 = getelementptr inbounds [4 x i32], [4 x i32]* %6, i64 0, i64 0

    • 第一个 i64 0,我们使用了基本类型 [4 x i32]、因此返回的指针前进 0 x 4 x (32/4) (0 * 16字节)= 0字节,也就是当前数组的首地址。
    • 第二个 i64 0 ,我们使用的基本类型是i32,返回的指针前进0字节,也就是当前数组的第一个元素。返回的指针类型为i32 *
  • 经过上面的学习、我们开始着手分析该章节开始的 makeInc 。对案例代码进行转换IR转换、
      • 先找到main 函数中的 makeIncrementer() 函数、从它开始着手
  1. define i32 @main(i32 %0, i8** %1) #0 {
  2. entry:
  3. %2 = bitcast i8** %1 to i8*
  4. %3 = call swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer() -> () -> Swift.Int"()
  5. ....
  6. }
      • 根据函数调用 @"main.makeIncrementer() -> () -> Swift.Int"() 找到 makeIncrementer() 内部实现代码
  1. define hidden swiftcc { i8*, %swift.refcounted* } @"main.makeIncrementer() -> () -> Swift.Int"() #0 {
  2. entry:
  3. %runningTotal.debug = alloca %TSi*, align 8
  4. %0 = bitcast %TSi** %runningTotal.debug to i8*
  5. call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
  6. //3、由swift_allocObject 分配出来的HeapObject对象 给到 %1
  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
  8. //4、将 %1 转换为 <{ %swift.refcounted, [8 x i8] }>*指针类型、其中又包含了一个swift.refcounted结构体,并且分配了 8xi8也就是8字节内存空间、该空间存储的也就是我们的数值
  9. %2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>* //9、<{ %swift.refcounted, [8 x i8] }>结构体为当前所创建出来的box
  10. %3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
  11. //7、连续的8字节地址空间
  12. %4 = bitcast [8 x i8]* %3 to %TSi*
  13. store %TSi* %4, %TSi** %runningTotal.debug, align 8
  14. //6、%TSi 为当前数组;数组的指针%TSi*; 第一个i32 0为TSi*类型首地址、第二个i32 0 取其中的第一个元素 i8
  15. %._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
  16. //5、将 i64 类型的数值 10 存储到 %._value中; 8、相当于将 10 存放到连续的8字节内存地址空间中
  17. store i64 10, i64* %._value, align 8
  18. %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
  19. call void @swift_release(%swift.refcounted* %1) #1
  20. //2、往 { i8*, %swift.refcounted* } 插入值;bitcast:unsafeBitCast按位转换、把当前的内嵌函数incrementr #1 () -> Swift.Int (i64)转换为 i8*
  21. //也就是放入void * 8字节内存空间中、意味着当前的void *存的是我们内嵌函数的地址;%swift.refcounted* %1,1 又插入一个 %1;
  22. //10、也就是将%1的HeapObject对象放到了结构体数据中
  23. %6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"partial apply forwarder for incrementr #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
  24. //1、函数调用后返回值的结果为一个结构体:第一个元素为 i8类型的指针(理解为 void*),第二个元素为 swift.refcounted 结构体类型的指针。并将 %6 该结构体类型返回
  25. ret { i8*, %swift.refcounted* } %6
  26. }
    • 其中涉及的定义
  1. %swift.function = type { i8*, %swift.refcounted* }
  2. %swift.refcounted = type { %swift.type*, i64 } // { (i64)* , i64} 该结构也就是我们的HeapObject对象
  3. %swift.type = type { i64 }
  4. %swift.full_type = type { i8**, %swift.type }
  5. %swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
  6. %swift.bridge = type opaque
  7. %Any = type { [24 x i8], %swift.type* }
  8. %TSi = type <{ i64 }>
      • 1、函数调用后返回值的结果为一个结构体:第一个元素为 i8类型的指针(理解为 void*),第二个元素为 swift.refcounted 结构体类型的指针。并将 %6 该结构体类型返回
      • 2、往 { i8*, %swift.refcounted* } 插入值;bitcast:unsafeBitCast按位转换、把当前的内嵌函数incrementr #1 () -> Swift.Int (i64)转换为 i8* 也就是放入void * 8字节内存空间中、意味着当前的void *存的是我们内嵌函数的地址;后边 %swift.refcounted* %1 又插入一个 %1、
      • 3、由swift_allocObject 分配出来的HeapObject对象 给到 %1、这时候我们查看 swift.refcounted 的定义
      • 4、将 %1 转换为 * 指针类型、其中又包含了一个swift.refcounted结构体,并且分配了 8xi8也就是8字节内存空间
      • 5、将 i64 类型的数值 10 存储到 %._value中;
      • 6、%TSi 为当前数组;数组的指针%TSi*; 第一个i32 0为TSi*类型首地址、第二个i32 0 取其中的第一个元素 i8
      • 7、连续的8字节地址空间
      • 8、相当于将 10 存放到连续的8字节内存地址空间中
      • 9、结构体为当前所创建出来的box容器
      • 10、第2条的 %swift.refcounted* %1,1 也就是将%1的HeapObject对象放到了结构体数据中
    • 根据 makeIncrementer() 函数的返回值我们推断该结构体的组成部分
  1. struct HeapObject {
  2.     var type:UnsafeRawPointer
  3.     var refCount:UInt64
  4. }
  5. struct FunctionData<BoxType>{
  6.     var ptr:UnsafeRawPointer
  7.     var captureValue:BoxType //BoxType代表范型T
  8. }
  9. struct Box<T> {
  10.     var refCounted:HeapObject
  11.     var value:T
  12. }
  13. //这里必须包裹一层、不然当前的函数类型被当作泛型参数传递之后会被重新包裹一层;
  14. struct VoidIntFunc {//包装一个f、使我们的返回值不受影响
  15.     var f: () -> Int //makeIncrementer() 的返回值
  16. }
  17. var makeInc = VoidIntFunc(f:makeIncrementer())
  • 与此同时、我们可以将makeInc 内存地址绑定到结构体中;
    • 我们包装一个 f闭包返回值、使返回值不受影响、然而如下方式仍然无法使用
  1. let ptrr = UnsafeMutablePointer<FunctionData<Box<Int>>>.allocate(capacity: 1)
  2. //Cannot convert value of type 'VoidIntFunc' to expected argument type 'FunctionData<Box<Int>>'
  3. ptrr.initialize(to: makeInc)
  • 所以需要这样操作:
    • 因为 ptrr 指向我们的 f 返回值闭包地址、所以需要重新绑定内存;通过f得到一个内存空间、然后再把这块内存空间重新绑定到具体的结构体上。
  1. var makeInc = VoidIntFunc(f:makeIncrementer())
  2. var ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)
  3. ptr.initialize(to: makeInc)
  4. let  context = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) {
  5.     $0.pointee
  6. }
  7. print(context.ptr) //0x00000001000056a0
  8. print(context.captureValue.value) //7307466919713137513
  9. print("end")//断点
  • 通过 context.ptr的地址0x00000001000056a0 使用当前工程对应的可执行文件、获取内联函数地址堆的符号 _$s12ClosureInner15makeIncrementerSiycyF10incrementrL_SiyFTA:得证
  1. $ nm ~/Library/Developer/Xcode/DerivedData/StructIR-alsccwttwgnzaobtshifjxvyffzg/Build/Products/Debug/ClosureInner | grep 00000001000056a0
  2. 00000001000056a0 t _$s12ClosureInner15makeIncrementerSiycyF10incrementrL_SiyFTA
    • 若附上命名符号还原则清晰可见 incrementr #1 () -> Swift.Int in ClosureInner.makeIncrementer() -> () -> Swift.Int
  1. $ nm ~/Library/Developer/Xcode/DerivedData/StructIR-alsccwttwgnzaobtshifjxvyffzg/Build/Products/Debug/ClosureInner | grep 00000001000056a0 | xcrun swift-demangle
  2. 00000001000056a0 t partial apply forwarder for incrementr #1 () -> Swift.Int in ClosureInner.makeIncrementer() -> () -> Swift.Int

五、自动闭包

  • 自动闭包:使用 @autoclosure 将当前的表达式声明成一个自动闭包,不接收任何参数,返回值是当前内部表达式的值。
  • 引入自动闭包、我们先看这样一个案例:
  1. func debugOutPrint(_ condition:Bool ,_ message:String) {
  2.       if condition {
  3.           print("\(message)")
  4.       }
  5.  }
  6. debugOutPrint(ture, "Application Error Occured") //Application Error Occured
  • 上述代码会在当前 condition 为true的时候,打印我们当前的错误信息,也就意味着false的时候当前条件不会执行。
  • 如果我们当前的字符串可能是某个业务逻辑功能中获取的,比如下面这样的:
  1. func debugOutPrint(_ condition:Bool ,_ message:String) {
  2.         if condition {
  3.             print("\(message)")
  4.         }
  5.     }
  6.     func doSomething() -> String {
  7.         print("test method")
  8.         return "Application Error Occured"
  9.     }
  10.  debugOutPrint(false, doSomething()) //test method
  • 这个时候我们查看运行结果发现,当前的condition 无论是true还是false,当前的 doSomething 方法都会执行。
  • 如果当前的doSomething 是一个耗时的任务操作,那么这里就造成了一定的资源浪费。
    • 针对上述情况、我们想到的是把当前的参数修改成一个闭包
  1. func debugOutPrint(_ condition:Bool ,_ message: () -> String) {
  2.     if condition {
  3.         print(message())
  4.     }
  5. }
  6. func doSomething() -> String {
  7.     print("doSomething method")
  8.     return "Application Error Occured"
  9. }
  10. debugOutPrint(false, doSomething) //
  11. debugOutPrint(true, doSomething) //doSomething method \n Application Error Occured
  • 这样的话我们就能够正常在当前条件满足的时候调用我们当前的 doSomething 方法了、并且在不满足条件的时候无任何输出。
    • 然而、但是、奇葩的来了:如果多人合作开发、有人想通过传入一个String 来获取同样的结果、该怎么办呢?
  1. debugOutPrint(true, doSomething())
  2. debugOutPrint(true, "Application Error Occured")
  • 此时我们引入自动闭包来解决这个问题
    • 在上一个案例中、我们使用 @autoclosure 将当前的表达式声明成一个自动闭包,不接收任何参数,返回值是当前内部表达式的值。
    • 所以实际上我们传入的String 就是放入到一个闭包表达式中,在调用的时候返回。
  1. { //大概酱紫
  2. "Application Error Occured String"
  3. }
  1. func debugOutPrint(_ condition:Bool ,_ message: @autoclosure () -> String) {
  2.     if condition {
  3.         print(message())
  4.     }
  5. }
  6. func doSomething() -> String {
  7.     print("doSomething method")
  8.     return "Application Error Occured"
  9. }
  10. debugOutPrint(true, doSomething())
  11. debugOutPrint(true, "Application Error Occured String")
    • 输出结果如下、解决了上述需求。
  1. doSomething method
  2. Application Error Occured
  3. Application Error Occured String

 



所属网站分类: 技术文章 > 博客

作者:临摹

链接:http://www.phpheidong.com/blog/article/89474/eff301a7a92a7f8830b3/

来源:php黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

23 0
收藏该文
已收藏

评论内容:(最多支持255个字符)