10、Swift闭包

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

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

二、尾随闭包:

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

1、省略参数类型

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

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

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

3、省略 return 关键字

closureAfter(10, 20, 30) { (item1, item2, item3)in
    item1 + item2 < item3
}

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的尾随闭包可以用最简的一个 < 来返回闭包值:
var array = [1,2,4,3,0]
array = array.sorted(by: <)
print(array)	//[0, 1, 2, 3, 4]

三、闭包的捕获值

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

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

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

10、Swift闭包_第1张图片

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

10、Swift闭包_第2张图片

10、Swift闭包_第3张图片

综上总结:

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

四、闭包是引用类型

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

10、Swift闭包_第4张图片

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

define void @munge(%struct.munger_struct* %0) #0 {
//5、%2创建了一片内存空间、存放的是我们当前 struct.munger_struct结构体 的地址: 所以当前的%2是一个地址的地址(二级指针)
  %2 = alloca %struct.munger_struct*, align 8
 		 //由%struct.munger_struct** %2 可见 %2 二级指针
  store %struct.munger_struct* %0, %struct.munger_struct** %2, align 8
  		// %3 = 当前数组的首地址
  %3 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  //8、访问当前的p[1] :  当前index = 1,1 x 结构体大小(4字节)、访问我们数组当中的第二个元素p[1]
  %4 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
  //9、访问数组中第二个结构体的第一个成员f1
  %5 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %4, i32 0, i32 0
  //14、	%6 = 访问 p[1].f1
  %6 = load i32, i32* %5, align 4
  //15、当前数组的首地址
  %7 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  //10、访问当前的p[2]: 当前的 index = 2, 2 x 结构体大小(4字节),访问我们数组中的第三个元素p[2]
  %8 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %7, i64 2
  //11、访问数组中的第三个结构体的第二个成员f2
  %9 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %8, i32 0, i32 1
  //16、	%10 = 访问 p[2].f2
  %10 = load i32, i32* %9, align 4
  //17、	%11 = p[1].f1+p[2].f2
  %11 = add nsw i32 %6, %10
  //4、 %12 访问的是我们的 %2。        6、取出%2的首地址给%12;此时%12存放的是当前数组的首地址
  %12 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  //3、 当前返回值类型位 %struct.munger_struct结构体类型、需要拿到数组的基地址,%struct.munger_struct* %12是当前索引结构体的地址、i64 0 当前数组的index;
  %13 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %12, i64 0 		//12、访问当前数组的第一个元素p[0]
  //7、%13 是我们当前数组的第一个结构体元素;第一个i32 0 相当于结构体指针偏移0字节,也就是不偏移,第二个i32 0 表示第一个结构体成员											
  %14 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %13, i32 0, i32 0 	//13、访问数组中第一个结构体的第一个成员f1
  //18、 p[0].f1 = p[1].f1+p[2].f2
  store i32 %11, i32* %14, align 4
  ret void
}
    • 代码中我们从左往右看、而底层程序理解过程中则是从右往左看,所以最后的 %12、%13、%14才是对我们的 p[0].f1进行赋值操作
      • 我们结合下面的案例和图片来对取数组首元素进行理解
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代码应该是这样的: 
%6 = alloca [4 x i32], align 16
%9 = getelementptr inbounds [4 x i32], [4 x i32]* %6, i64 0, i64 0

10、Swift闭包_第5张图片

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

五、自动闭包

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

 

你可能感兴趣的:(#,Swift进阶,Swift闭包本质,IR基础语法,闭包的捕获值,闭包是引用类型)