一、闭包是⼀个捕获了上下⽂的常量或者是变量的函数。
-
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代表什么意思呢?
-
- alloc_box: 在堆上分配一块内存空间,存储了metadata,refCount,当前的 value。
-
- 所以我们捕获值的本质就是:在堆上开辟一块空间、把我们的变量放到其中。当前闭包是内嵌函数+捕获上下文的变量/常量;我们可以通过汇编调用来验证
综上总结:
-
-
- ⼀个闭包能够从上下⽂捕获已被定义的常量和变量。即使定义这些常量和变量的原作⽤域已经不存在, 闭包仍能够在其函数体内引⽤和修改这些值。
- 当我们每次修改的捕获值的时候,修改的是堆区中的 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我们也没有看出他的具体内容
- 这个时候我们把我们的 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 <elementtype>]
//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 { <type list> } ;
//正常结构体类型
-
%
T2 = type <{ <type list> }> ;
//封装结构体类型
-
//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* 代替。
-
<type> *
-
i64*
//指向 i64类型 的指针
-
[
4 x i32]*
//指向 4个i32类型的数组 的指针
-
i32 (i32*) *
//指向 参数为i32、返回值为i32的函数 的指针
-
i32 addrspace(
5)*
//指向 在内存地址5中的i32类型 的指针
- getelementptr指令:LLVM中我们获取数组和结构体的成员,通过 getelementptr;它只执行地址计算,不访问内存。
-
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
-
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
-
<result> = getelementptr <ty>, <ptr vector> <ptrval>, [inrange] <vector index type> <idx>
-
-
- 第⼀个索引不会改变返回的指针的类型,也就是说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进行赋值操作
- 我们结合下面的案例和图片来对取数组首元素进行理解
- 代码中我们从左往右看、而底层程序理解过程中则是从右往左看,所以最后的 %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
-
- 第一个 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<BoxType>{
-
var ptr:
UnsafeRawPointer
-
var captureValue:
BoxType
//BoxType代表范型T
-
}
-
struct Box<T> {
-
var refCounted:
HeapObject
-
var value:
T
-
}
-
//这里必须包裹一层、不然当前的函数类型被当作泛型参数传递之后会被重新包裹一层;
-
struct VoidIntFunc {
//包装一个f、使我们的返回值不受影响
-
var f: () ->
Int
//makeIncrementer() 的返回值
-
}
-
var makeInc =
VoidIntFunc(f:makeIncrementer())
- 与此同时、我们可以将makeInc 内存地址绑定到结构体中;
- 我们包装一个 f闭包返回值、使返回值不受影响、然而如下方式仍然无法使用
-
let ptrr =
UnsafeMutablePointer<
FunctionData<
Box<
Int>>>.allocate(capacity:
1)
-
//Cannot convert value of type 'VoidIntFunc' to expected argument type 'FunctionData<Box<Int>>'
-
ptrr.initialize(to: makeInc)
- 所以需要这样操作:
- 因为 ptrr 指向我们的 f 返回值闭包地址、所以需要重新绑定内存;通过f得到一个内存空间、然后再把这块内存空间重新绑定到具体的结构体上。
-
var makeInc =
VoidIntFunc(f:makeIncrementer())
-
var ptr =
UnsafeMutablePointer<
VoidIntFunc>.allocate(capacity:
1)
-
ptr.initialize(to: makeInc)
-
let context = ptr.withMemoryRebound(to:
FunctionData<
Box<
Int>>.
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
转载:https://blog.csdn.net/SharkToping/article/details/117339443
查看评论