swift-闭包(1)
一、函数类型
函数本身也有自己的类型,它由形式参数和返回类型组成,我们以一个例子说明:addTwoInts由于形式参数和返回值类型的不同产生了不同的函数类型,在调用函数时就需要说明调用的是什么形式参数和返回值类型的函数。
func addTwoInts(_ a: Double, _ b: Double) -> Double { return a + b } func addTwoInts(_ a: Int, _ b: Int) -> Int { return a + b } var a: (Double, Double) -> Double = addTwoInts a(10, 20) var b = a b(20 ,30)
二、闭包(closure)的定义
一中讲函数类型就是为闭包的定义做铺垫,闭包是一个(捕获了上下文的常量或者是变量的)(函数)。
func makeIncrementer() -> () -> Int { var runningTotal = 10 func incrementer() -> Int { runningTotal += 1 return runningTotal } return incrementer }
对于闭包来说
- 一般指定义在函数内部的函数
- 一般捕获的是外层函数的局部变量、常量
我们可以把闭包想象成一个类的实例对象
- 存储在堆区
- 捕获的局部变量、常量就是对象的成员(存储属性)
- 组成闭包的函数就是类内部定义的方法
闭包类似于OC中的block或者是其他语言中的一些匿名函数,是自包含的功能代码块
- 闭包可以作为函数的参数,也可以作为函数的返回值
- 可以用于回调和反向传值
三、闭包表达式
闭包表达式可以理解为闭包的表达形式:
{(param) -> (returnType) in //函数体代码 }
- 参数可以有多个
- 参数列表的小括号可以省略
- 返回值类型也可以省略
- 当没有返回值时 in可以省略
- in可以看作一个分隔符,将函数体和前面的参数,返回值分隔开
3.1 闭包作为变量或者常量
var age = 10 //闭包作为变量 var closure:(Int)->Int = {(age:Int) in return age } //闭包作为常量 let closure:(Int)->Int closure = {(age :Int) in return age }
3.2 可选闭包
//错误的写法 var closure : (Int) -> Int? //正确的写法 var closure : ((Int) -> Int)? closure = nil
3.3 闭包作为函数的参数
func closureFounction(param:(_ a: Int, _ b: Int) -> Int) { print("闭包返回值:\(param(100, 200))") //输出 100 } closureFounction(param: {(num1, num2) -> (Int) in print("闭包执行了") return 100})
//闭包执行了
//闭包返回值:100
3.4 闭包作为函数的返回值
func closureFounction(_ a: Int, _ b: Int) -> (_ a: Int) -> (Int) { return closure2 }
四、尾随闭包
当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很长,我们可以通过尾随闭包的书写方式来提高代码的可读性。
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: Int, _ item2: Int, _ item3: Int) in return item1 + item2 < item3 }
var array = [1, 2, 3] array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })(1) array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })(2) array.sort(by: {(item1, item2) in return item1 < item2 })(3) array.sort{(item1, item2) in item1 < item2 }(4) array.sort{ return $0 < $1 }(5) array.sort{ $0 < $1 }(6)//推荐写法 array.sort(by: <)(7)
- 利用上下文推断参数和返回值类型(1)-(3)
- 单表达式可以隐式返回,可省略return关键字 (3)-(4)
- 参数的名称简写 (5)-(6)
- 尾随闭包表达式(7)
四、闭包捕获值
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:
在闭包内部会截取到i值,并且在调用i+a(a = 1)时,会同时改变外部i值,我们通过SIL来看一下这个调用过程。
4.1 SIL 分析
所以我们可以得出结论,在闭包调用捕获外部局部变量的时候,是把值捕获到了堆区,在使用的时候直接访问拿到的值。
- 闭包可以在方法调用的时候捕获上下文的常量或者变量
- 即使这个方法的作用域已经不在,仍然可以修改捕获到的变量
4.2 block和闭包的区别
- Block可分为全局Block、堆Block、栈Block
- 闭包不做区分,闭包没有捕获值的功能,他拿的就是全局变量的地址
4.3 全局Block、堆Block、栈Block
- 全局Block不使用外部变量,只使用静态变量和全局变量
- 栈Block使用局部变量或者oc属性,但没有赋值给强引用
- 堆Block使用了局部变量或者oc属性,且赋值给了强引用
- (void)testBlock{ NSObject *o = [NSObject new]; NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)o));//1 //堆Block void(^strongBlock)(void) = ^{ NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)o));//3 }; strongBlock(); //栈Block void(^__weak weakBlock)(void) = ^{ NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)o));//4 }; weakBlock(); //堆Block -> 栈Block要加两次引用计数、weakblock一次、copyblock一次 // void(^copyBlock)(void) = [weakBlock copy]; // copyBlock(); } // 1 // 3 // 4
五、闭包的本质(是结构体,但是是引用类型)
在分析闭包的本质之前,先学习一下IR语法
数组
[<elementnumber> x <elementtype>] //example alloca[24 x i8],align 8 24个i8都是0 alloca[4 x i32] === array
- i8一般指Int或者void *
- i32是32位的整形
结构体
%swift.refcounted = type{ %swift.type*,i64} //表示形式 %T = type{<type list>}//与c语言结构类似
指针类型
<type >* //example i64* //64位的整形
5.1 getelementptr指令
在LLVM中我们获取数组和结构体的成员,通过 getelementpt ,语法规则如下:
<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; } //指向结构体的首地址 getelementptr inbounds %struct.munger_struct , %struct.munger_struct %1 ,i64 0 //指向第一个元素的地址 getelementptr inbounds %struct.munger_struct , %struct.munger_struct %1 ,i64 0, i32 0 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字节 */
- 第一个索引不会改变指针的类型,即*前面是什么类型,返回就是什么类型
- 第一个索引的偏移量是有第一个索引的值和第一个ty指定的基本类型共同确定的
- 后面的索引是在数组或者结构体内进行索引
- 每增加一个索引,就会使得该索引使用的基本类型和返回的指针类型去掉一层(例如 [4 x i32] 去掉的一层就是 i32)
5.2 从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
,也就是说闭包就是这样的结构。