闭包表达式(Closure Expresssion)

在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数

  1. /// 方法
  2. func sum(_ v1: Int, _v2: Int) -> Int {
  3. v1 + v2
  4. }
  5. /// 闭包
  6. var fn = {
  7. (v1: Int, v2: Int) -> Int in
  8. return v1 + v2
  9. }

闭包结构:

  1. {
  2. (参数列表) -> 返回值类型 in
  3. 函数体代码
  4. }

闭包表达式的简写

  1. /// 定义一个函数,有三个参数:v1、v2、一个函数
  2. func test(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
  3. print(fn(v1, v2))
  4. }
  5. /// 将函数用闭包代替
  6. test(v1: 10, v2: 20, fn: { (v1: Int, v2: Int) in
  7. return v1 + v2
  8. })
  9. /// 简写闭包
  10. test(v1: 10, v2: 20, fn: { v1, v2 in
  11. return v1 + v2
  12. })
  13. /// 去掉return
  14. test(v1: 10, v2: 20, fn: { v1, v2 in
  15. v1 + v2
  16. })
  17. /// 用美元符代替 $0、$1
  18. test(v1: 10, v2: 20, fn: {$0 + $1})
  19. /// 直接写一个加号
  20. test(v1: 10, v2: 20, fn: +)

尾随闭包

如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
尾随闭包就是被书写在函数调用括号外面(后面)的闭包表达式

  1. /// 函数
  2. func test(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
  3. print(fn(v1, v2))
  4. }
  5. /// 调用
  6. test(v1: 10, v2: 20) {
  7. $0 + $1
  8. }

如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号

  1. func test(fn: (Int, Int) -> Int) {
  2. print(fn(1, 2))
  3. }
  4. /// 调用
  5. test {
  6. $0 + $1
  7. }

示例 - 数组的排序

  1. @inlinable public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
  1. var arr = [10, 1, 2, 8]
  2. // 正常闭包表达式
  3. arr.sort(by: {v1, v2 in
  4. return v1 > v2
  5. })
  6. // 尾随闭包
  7. arr.sort { v1, v2 in
  8. return v1 > v2
  9. }
  10. // 省略return
  11. arr.sort { v1, v2 in
  12. v1 > v2
  13. }
  14. // 用美元符代替
  15. arr.sort {
  16. $0 > $1
  17. }
  18. // 直接用大于号代替
  19. arr.sort(by: >)

闭包(Closure)

一个函数和它捕获的变量/常量环境组合起来,称为闭包
一般定义在函数内部的函数
一般它捕获的外层函数的局部变量/常量

  1. typealias Fn = (Int) -> Int
  2. func getFn() -> Fn {
  3. var num = 0
  4. func plus(_ i: Int) -> Int {
  5. num += i
  6. return num
  7. }
  8. return plus
  9. }
  10. var fn = getFn()
  11. print(fn(1)) // 1
  12. print(fn(2)) // 3
  13. print(fn(3)) // 6
  14. print(fn(4)) // 10

按道理说num是局部变量,在函数执行结束时就会被销毁,但是多次调用fn,num的值却是累加的,是因为getFn函数内部的plus函数和局部变量num形成了闭包,捕获了局部变量num,num被保存在了堆空间,所以调用fn时num没有被销毁,可以通过汇编代码观察,调用了alloc方法,开辟了堆空间的内存,保存了num

  1. SwiftTest`getFn():
  2. 0x100004050 <+0>: pushq %rbp
  3. 0x100004051 <+1>: movq %rsp, %rbp
  4. 0x100004054 <+4>: subq $0x10, %rsp
  5. 0x100004058 <+8>: movq $0x0, -0x8(%rbp)
  6. 0x100004060 <+16>: leaq 0x40e9(%rip), %rdi
  7. 0x100004067 <+23>: movl $0x18, %esi
  8. 0x10000406c <+28>: movl $0x7, %edx
  9. 0x100004071 <+33>: callq 0x100007718 ; symbol stub for: swift_allocObject
  10. 0x100004076 <+38>: movq %rax, %rdi
  11. 0x100004079 <+41>: movq %rdi, -0x10(%rbp)
  12. 0x10000407d <+45>: movq %rdi, %rax
  13. 0x100004080 <+48>: addq $0x10, %rax
  14. 0x100004084 <+52>: movq %rax, -0x8(%rbp)
  15. 0x100004088 <+56>: movq $0x0, 0x10(%rdi)
  16. -> 0x100004090 <+64>: callq 0x10000777e ; symbol stub for: swift_retain
  17. 0x100004095 <+69>: movq -0x10(%rbp), %rdi
  18. 0x100004099 <+73>: callq 0x100007778 ; symbol stub for: swift_release
  19. 0x10000409e <+78>: movq -0x10(%rbp), %rdx
  20. 0x1000040a2 <+82>: leaq 0x167(%rip), %rax ; partial apply forwarder for plus(Swift.Int) -> Swift.Int at <compiler-generated>
  21. 0x1000040a9 <+89>: addq $0x10, %rsp
  22. 0x1000040ad <+93>: popq %rbp
  23. 0x1000040ae <+94>: retq

如果创建另一个fn变量,执行结果如下:

  1. var fn1 = getFn()
  2. var fn2 = getFn()
  3. print(fn1(1)) // 1
  4. print(fn2(2)) // 2
  5. print(fn1(3)) // 4
  6. print(fn2(4)) // 6

因为fn1和fn2都分配的新的堆空间地址。
总结:
可以把闭包想象成是一个类的实例对象
内存在堆空间
捕获的局部变量/常量就是对象的成员(存储属性)
组成闭包的函数就是类内部定义的方法

注意

如果返回值类型是函数类型,那么参数的修饰要保持统一

自动闭包

  1. /// 如果第一个参数大于0,返回第一个数。否则返回第2个数
  2. func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
  3. return v1 > 0 ? v1 : v2()
  4. }
  5. getFirstPositive(10, 20) // 10
  6. getFirstPositive(-2, 20) // 20

@autoclosure 会自动将第二个参数封装成闭包表达式{ 20 },如果如果第一个参数条件成立,就不会调用第二个参数闭包
@autoclosure 只支持 ( ) -> T 格式的参数
@autoclosure 并非只支持最右一个参数
空合并运算符 ?? 使用了 @autoclosure 技术