闭包表达式(Closure Expression)
可以通过func定义一个函数,也可以通过闭包表达式定义一个函数
闭包表达式调用可以直接省略参数名
闭包定义函数也可以写为:
闭包表达式的简写
尾随闭包
如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式
上面的函数使用闭包调用:
如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号
针对上面的函数,一般与闭包调用的对比:
示例 - 数组的排序
数组排序的底层代码的定义:
由上面的底层代码可知,想要自定义一个数组排序,只需要传入一个有两个参数,返回值是Bool的函数
自定义排序方法的多种调用方式(闭包和简写):
闭包(Closure)
网上有各种关于闭包的定义,个人觉得比较严谨的定义是 :
- 一个函数和它所捕获的变量\常量环境组合起来,称为闭包
- 一般指定义在函数内部的函数
- 一般它捕获的是外层函数的局部变量\常量
定义了一个闭包:
注:return plus前分配了一段堆空间,将num的值存储到了这个堆空间,调用plus访问的num实际上是堆空间的num, plus方法实际接收了两个参数:i和堆空间地址
使用反汇编看它实现的底层汇编代码:
由图上红色框标注的汇编代码我们可以看出,在闭包里调用了swift_allocObject方法,我们可以理解为它在堆区申请了一块内存空间,用来存储它捕获的外层函数的局部变量或常量也就是num,这也就决定了num不会立刻销毁,由此我们可以看下它的计算结果:
func getFn() -> Fn {
var num = 0
func plus(_ i: Int) -> Int{
num += 1
return num
}
return plus
}
var fn = getFn()
print(fn(1)) //结果为1
print(fn(2)) //结果为3
print(fn(3)) //结果为6
print(fn(4)) //结果为10
可以看出计算结果是累加的,那就因为num存储在堆空间,没有销毁,上面的计算是对num的不断累加。
注意:如果num是全局变量,则不会在堆空间开辟内存。
注意:每调用一次getFn()都会申请一个新的内存空间,举例如下:
由上面的计算可以看出,fn1和fn2是分开独立累加的,也就是fn1和fn2分别开辟了一块新内存,互不影响
反汇编代码如下:
由上面的反汇编代码可以看出fn1和fn2前8个字节相同,因为都是指向同一个plus方法,但是后8个字节不同,因为分配的堆空间不同。
- 可以把闭包想象成是一个类的实例对象
- 内存在堆空间
- 捕获的局部变量\常量就是对象的成员(存储属性)
- 组成闭包的函数就是类内部定义的方法
闭包举例
上面的闭包可以看成一个类:
上面的闭包同样可以看成一个类:
参数注意
如果返回值类型是函数类型,那么参数的修饰要保持统一
自动闭包
- @autoclosure 会自动将 20 封装成闭包 { 20 }
- @autoclosure 只支持 () -> T 格式的参数
- @autoclosure 并非只支持最后1个参数
- 空合并运算符 ?? 使用了 @autoclosure 技术
- 有@autoclosure、无@autoclosure,构成了函数重载
为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚:这个值会被推迟执行。