1.闭包的定义
一个函数和它所捕获的变量、常量环境组合起来,被称为闭包
-
一般指定义在函数内部的函数
-
一般捕获的是外层函数的局部变量、常量
func makeClosure() -> (Int) -> Int {
var one = 100 func makeClosure(num number:Int) -> Int { one += number return one } return makeClosure //返回的makeClosure和one形成了闭包}
可以把闭包想象成一个类的实例对象
- 内存在堆空间
- 捕获的局部变量、常量就是对象的成员(存储属性)
- 组成闭包的函数就是类内部定义的方法
闭包是自包含的功能代码块,跟C和OC中的代码块(block)和其他一些语言中的匿名函数相似
- 闭包可以作为函数的参数也可以作为函数的返回值
- 可以向
OC中一样用于回调和反向传值
2.闭包表达式
闭包表达式可以理解为闭包的表现形式
{(param) -> (returnType) in
//函数体代码
}
- 参数有多个是,多个参数用逗号隔开
- 参数列表的小括号可以省略
- 返回值类型也可以省略
- 当没有返回值是
in可以省略 in可以看做是一个分隔符,将函数体和前面的参数、返回值分隔开
2.1、闭包作为变量或常量
var closure: (_ a: Int, _ b: Int) -> Int = {(a, b) -> Int in
return 10
}
let closure1: (_ a: Double) -> Double = { a -> Double in
return 100.0
}
var x = closure(10, 20)
var y = closure1(200.0)
需要注意的一点是:
//错误的写法
var closure : (Int) -> Int? //正确的写法
var closure : ((Int) -> Int)? closure = nil
2.2、闭包作为函数的参数
func closureFounction(param:(_ a: Int, _ b: Int) -> Int) {
print("闭包返回值:\(param(100, 200))") //输出 100
}
closureFounction(param: {(num1, num2) -> (Int) in
print("闭包执行了")
return 100})
}
2.3、闭包作为返回值
func closureFounction(_ a: Int, _ b: Int) -> (_ a: Int) -> (Int) {
return closure2
}
3.尾随闭包
当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很长,我们可以通过尾随闭包的书写方式来提高代码的可读性。
func closureFounction(_ a: Int, _ b: Int, _ by:(_ x:Int) -> Int) {
var num = by(20)
}
closureFounction(20, 20){ (x) in
return 30
}
var array = [1, 2, 3]
array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
array.sort(by: {(item1, item2) in return item1 < item2 })
array.sort{(item1, item2) in item1 < item2 }
array.sort{ return $0 < $1 }
array.sort{ $0 < $1 }
array.sort(by: <)
太过于简单的写法不推荐,因为不利于代码阅读以及以后的维护
4.闭包捕获值
func make() {
var i = 1
let closure: (_ a: Int) -> () = { (a) in
i += a
print("closure:\(i)")
}
print(i)
closure(1)
print("after1:\(i)")
closure(1)
print("after2:\(i)")
closure(1)
print("after3:\(i)")
closure(1)
print("after4:\(i)")
}
make()
//输出结果
1
closure:2
after1:2
closure:3
after2:3
closure:4
after3:4
closure:5
after4:5
从输出结果看,在闭包内部调用给 i + 1 可以改变外部 i 的值,这个调用过程是怎么样的过程呢,来分析一下:
4.1、SIL分析
通过命令把项目中的main.swift编译为main.sil并打开,定位到make()函数位置并找到对应的闭包的实现位置
所以我们可以得出结论,在闭包调用捕获外部局部变量的时候,是把值捕获到了堆区,在使用的时候直接访问拿到的值。
- 闭包可以在方法调用的时候捕获上下文中的常量或变量。
- 即使这个方法的作用域已经不在,仍然可以修改捕获到的变量
4.2、全局变量和局部变量捕获时的区别
闭包在使用全局变量的时候不会捕获到闭包内部,而在使用局部变量的时候会捕获到闭包的内部再使用
-
使用全局变量
var a = 10 //全局变量 let closure = { a += 10 } closure()
通过SIL分析:
通过以上截图可以看出来,在使用全局变量的时候是直接使用,默认是不捕获的
- 使用局部变量
待完善.....
5.闭包的本质
在分析闭包的本质之前,先简单看一下IR的语法
-
数组
[ x ] //elementnumber数组中存放的数据的数量 //elementtype数组中存放的数据的类型 //例子 alloca [24 * i8], align 8 //24个int8类型都是0
-
结构体
/*
-
T:结构体名称
-
:列表,即结构体的成员列表*/%T = type {} //和C语言的结构体类似//例子 /*
-
swift.refcounted:结构体名称
-
%swift.type*:swift.type指针类型
-
i64:64位整型 - 8字节 / %swift.refcounted = type { %swift.type, i64}
-
-
指针
*
//例子 //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;
}
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/*
- [4 x i32]* array:数组首地址
- 第一个0:相对于数组自身的偏移,即偏移0字节 0 * 4字节
- 第二个0:相对于数组元素的偏移,即结构体第一个成员变量 0 * 4字节
*/
总结
- 第一个索引不会改变返回的指针的类型,即
ptrval前面的*对应什么类型,返回的就是什么类型 - 第一个索引的
偏移量是由第一个索引的值和第一个ty指定的基本类型共同确定的 - 后面的索引是在数组或者结构体内进行索引
- 每增加一个索引,就会使得该
索引使用的基本类型和返回的指针类型去掉一层(例如[4 x i32]去掉一层是i32)
5.1、从IR的角度分析结构体的本质
把下面这段代码编译为IR格式
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
所以根据以上代码可以还原出以下结构
struct ClosureData<Box>{
var ptr: UnsafeRawPointer
var object: UnsafePointer<Box>
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
struct Box<T>{
var object: HeapObject
var value: T
}
下面验证一下还原的数据结构
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
//{ i8*, %swift.refcounted* }
struct ClosureData<Box>{
var ptr: UnsafeRawPointer
var object: UnsafePointer<Box>
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
struct Box<T>{
var object: HeapObject
var value: T
}
struct ClosureStruct {//用结构体包裹一下闭包
var closure :() -> Int
}
var f = ClosureStruct(closure: makeIncrementer())
let ptr = UnsafeMutablePointer<ClosureStruct>.allocate(capacity: 1)//创建ClosureStruct类型的指针并分配一块内存空间
ptr.initialize(to: f)//内存空间初始化f
let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int>>.self, capacity: 1){
$0.pointee
}//内存重新绑定为 ClosureData<Box<Int>>
print(ctx.ptr)
print(ctx.object)
ptr.deinitialize(count: 1)
ptr.deallocate()//输出
0x0000000100002c20
0x0000000100779c10
打开终端验证
通过终端输出就能在mach-o文件中找到对应的内嵌函数incrementer,也就是说闭包就是这样的结构。