swift5-闭包

Posted by AndyCao on September 5, 2019

闭包表达式: closure expression

闭包表达式,其形式如下:

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

通过 in 将函数体代码和(参数列表与返回值类型)区分开来

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

  • func的形式
    func sum(_ v1: Int, _ v2: Int) -> Int {
      return v1 + v2 
    }
    sum(1, 2) // 3
    
  • 闭包表达式的形式
    var fn = {
      (v1: Int, v2: Int) -> Int in
      return v1 + v2
    }
    fn(1, 2) // 3
    

{
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}(1, 2) // 3

尾随闭包

尾随闭包是一个被书写在函数调用括号后面的闭包表达式

当一个很长的闭包表达式作为函数的最后一个实参时,使用尾随闭包,可以增强函数的可读性

func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}
// 函数调用
exec(v1: 1, v2: 2) { (v1, v2) -> Int in
    return v1 + v2
}
// 可以简写为
exec(v1: 1, v2: 2) {
    $0 + $1
}

数组排序

  • func的形式:
    // true: v1排在v2前面
    // false: v1排在v2后面
    func cmp(v1: String, v2: String) -> Bool {
      // 降序:大的排在前面
      return v1 > v2
    }
    var arr = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
    arr.sort(by: cmp)
    print(arr)
    // 打印结果为 ["Peter", "Kweku", "Kofi", "Akosua", "Abena"]
    
  • 闭包表达式的形式
    var arr = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
    arr.sort(by: {
      (v1: String, v2: String) -> Bool in
      return v1 > v2
    })
    arr.sort(by: {
      v1, v2 in return v1 > v2
    })
    arr.sort(by: {
      v1, v2 in v1 > v2
    })
    arr.sort(by: { $0 > $1 })
    arr.sort(by: >)
    // 尾随闭包
    arr.sort() { $0 > $1 }
    arr.sort { $0 > $1 }
    

闭包: closure

一个函数和它所捕获的变量\常量组合起来,称为闭包

  • 一般指定义在函数内部的函数
  • 一般它捕获的是外层函数的局部变量\常量
    typealias aClosure = (Int) -> Int
    func getClosure() -> aClosure {
      var num = 1
      func plus(_ i: Int) -> Int {
          num += i
          return num
      }
      return plus
    } // 返回的plus和num构成了闭包
    var ac = getClosure()
    // 下面打印的结果是什么?
    print(ac(1))
    print(ac(2))
    print(ac(3))    
    

    先看一下print的结果: 2 4 7

  • 在getClosure调用之后,得到一个闭包,赋给全局变量ac
  • getClosure中定义的局部变量num释放
  • 在print函数中,调用ac,对num进行+操作
  • 无异常,正常打印

大家有没有好奇,为什么没有抛异常?

我们先对plus函数做如下修改,使其没有捕捉外层函数的num变量

其汇编如下:

我们将其修改回来,断点打在相同的位置,此时的汇编如下:

  • 从汇编结果中可以看出,闭包在捕获变量之后,会通过allocObject函数,在堆中开辟出一段空间

  • 然后将num中的值拷贝一份到地址为(rax + 0x10)的堆空间中

  • 此后对num的操作,都是对堆空间中的值进行的操作

从下图可以看到num的值在堆空间中的具体变化过程

注意

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

func add(_ num: Int) -> (inout Int) -> Void {
    func plus(v: inout Int) {
        v += num
    }
    return plus;
}
var num = 5
add(20)(&num)
print(num) // 25

自动闭包:@autoclosure

func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive(10, 20)
  • @autoclosure 会自动将 20 封装成闭包 { 20 }
  • @autoclosure 只支持 () -> T 格式的参数
  • @autoclosure 并非只支持最后一个参数
  • 空合并运算符 ?? 使用了 @autoclosure 技术