闭包表达式
1.闭包在调用的时候,不需要写形参v1还是v2。
闭包作为函数参数
func exec(v1:Int,v2:Int,fn:(Int,Int) -> Int){
print(fn(v1,v2))
}
那么在调用exec函数的时候已下两种方式都可以,可以对两个参数做一些操作,最后输出结果。因为他们在闭包中的参数类型和返回值类型都确定了,并且都是Int类型,所以可以直接用1表示第二个参数。
exec(v1: 10, v2: 10) { v1, v2 **in**
v1 + v2
}
exec(v1: 10, v2: 20, fn: {$0+$1})
尾随闭包
闭包表达式很长,作为最后一个实参,使用尾随闭包可以增加可读性。具体是写在括号后面。
闭包
一般是发生在函数嵌套中,返回的是内部函数,并且内部函数捕获了外部函数的变量或者常量。
举个栗子
func outside() -> () -> () {
var a = 10
func inside() {
print(a)
a += 1
}
return inside
}
举个🌰
typealias Fn = (Int) -> Int
func getFn() -> Fn{
var num = 0
func plus(_ i:Int)->Int{
num += i
return num
}
return plus
}
let vn = getFn()
print(vn(1))
print(vn(2))
print(vn(3))
print(vn(4))
打印结果: 1 ,3 ,6, 10.
声明了一个函数的别名Fn,并且作为函数getFn的返回值类型,通过getFn可以拿到plus函数,然后通过给plus函数传值拿到返回值,然后通过print打印返回值。
问题就是,这个num作为局部变量,为什么可以在相加的时候,num保持了原有的数据而不是销毁回收了?
为了弄清楚这个问题,还是可以通过汇编的方式去分析,现在的问题就是访问num的时候,似乎找到了同一片内存空间,所以可以相加。先把num注释,看一下汇编的情况。
打印出的结果,leaq是返回了rip+0x15这个地址,也就是说返回的是这个plus方法的地址。
那么再加上num看一下
对比之前,可以看到有一步操作是swift_allocObject,也就是说去堆空间申请了一片地址,也就是说只要用到了num,就会开辟这块堆空间,getFn函数调用一次,就会alloc一次,因为用的是同一个vn,所以他们能够保证访问的都是那块堆空间的地址,num所以会累加。并且注意到这边有很多关于movq的操作,movq就是找到这个地址并且把地址里的数据拿出来。这里的操作就是把num的数据存到堆空间上。
分析闭包结构
既然vn = getFn(),也大概知道vn就是getFn里的plus函数,那么vn到底是什么呢?还是说只是一个函数而已,那么首先可以打印一下他的大小看一下。
如果没有捕获外部变量,这个vn的大小也是16个字节,前八个字节存储的是这个返回的方法的地址,后八字节是0.
如果是闭包,前八字节是与这个方法相关的地址,可以根据这个地址找到这个方法,后八个字节是堆空间的地址。在调用的时候,除了这个方法所需要的参数以外,还会传递后八个字节也就是堆空间的地址给这个方法。
闭包表达式 & 闭包
定义函数有两种方式,第一是func关键字定义,第二种就是闭包表达式定义,他们都是函数,有参数和返回值函数体,但是闭包除了函数以外,还有他所捕获的局部变量。局部变量被捕获以后,是将原来栈空间的这个变量里的内容拷贝到堆空间,以后访问直接到堆空间访问,保证这个闭包生成的变量下次访问还是这块堆空间的地址,随着函数结束,原来这块栈空间的局部变量一样还是会被回收。
自动闭包 @autoclosure
func getNum(_ v1:Int,_ v2:()->Int)->Int{
return v1 > v2() ? v1 : v2()
}
print(getNum(10, {20}))
如果传参里加入了闭包表达式,这样做的好处是可以延迟执行闭包函数,如果getnum的两个参数,后者是一个普通函数调用,那么他一定会执行,但是有的时候,我们希望通过内部判断,不让他执行,那么可以通过闭包直接在getNum里面做判断即可。
还有一种写法,也就是自动闭包的方式,@autoclosure 他会自动帮你加上{},并且返回一个类型,需要注意的是,自动闭包只接收()->T这种方式,但不需要一定是末尾参数才可以加,另外自动闭包是可以重载的。
func getNum(_ v1:Int,_ v2: @autoclosure ()->Int)->Int{
return v1 > v2() ? v1 : v2()
}
print(getNum(10, 20))