Swift小知识点之闭包

什么是闭包

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

    • 一般指定义在函数内部的函数

    • 一般它捕获的是外层函数的局部变量\常量

  • 可以把闭包想象成是一个类的实例对象

    • 内存在堆空间

    • 捕获的局部变量\常量就是对象的成员(存储属性)

    • 组成闭包的函数就是类内部定义的方法

  • 示例

    我们有一个函数 sum

    // 函数
    func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
    // 使用
    sum(10, 20)

    如果用闭包表达式定义一个函数

    var fn = {
        (v1: Int, v2: Int) -> Int in
        return v1 + v2
    }
    
    // 使用
    fn(10, 20)

    当然了,也可以

    {
      (v1: Int, v2: Int) -> Int in
        return v1 + v2
    }(10, 20)

    总结起来就是

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

闭包表达式的简写

我们定义如下的函数 exec ,它接收三个参数,分别为两个Int 和一个函数,而且这个函数,接收两个Int 参数,返回一个Int结果,exec 的作用就是,把前两个参数传给第三个参数(也就是函数)去执行,然后结果打印出来

func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}

如果用闭包表达式来定义的话

// 闭包表达式
exec(v1: 10, v2: 20, fn: {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
})

当然了,我们可以省略很多,如下

  • 省略参数类型 因为swift可以自己推断类型
    exec(v1: 10, v2: 20, fn: {
        v1, v2 in return v1 + v2
    })
  • return 也可以省略

    exec(v1: 10, v2: 20, fn: {
        v1, v2 in v1 + v2
    })
  • 省略掉参数列表,用$0代表第0个参数,$1代表第1个参数
    exec(v1: 10, v2: 20, fn: {
        $0 + $1
    })
  • 终极省略
    exec(v1: 10, v2: 20, fn: +)

尾随闭包

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

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

      有如下的函数 闭包表达式作为函数的最后一个实参

      func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
          print(fn(v1, v2))
      }

      使用尾随闭包为

      exec(v1: 10, v2: 20) {
          $0 + $1
      }
  • 如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号
    // 这个闭包表达式是函数的唯一实参
    func exec(fn: (Int, Int) -> Int) { 
        print(fn(1, 2))
    }

    可以使用尾随闭包如下

    // 使用尾随闭包如下三种都可以
    exec(fn: { $0 + $1 })
    exec() { $0 + $1 }
    exec { $0 + $1 }

尾随闭包实战

  • 系统自带的排序

    假设我们有个包含Int元素的数组,想对立面的元素进行排序

    func numberSort()  {
        var arr = [6, 8, 1, 10]
        arr.sort()
        print(arr) //[1, 6, 8, 10]
    }
    numberSort()

    查看官方对sort的源码为

    // 官方代码
    func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
  • 自定义排序

    假如我们想自定义排序

    /// 返回true: i1排在i2前面
    /// 返回false: i1排在i2后面
    func cmp(i1: Int, i2: Int) -> Bool {
        // 大的排在前面
        return i1 > i2
    }

    使用的时候

    var nums = [6, 8, 1, 10]
    nums.sort(by: cmp)
    print(nums) ///[10, 8, 6, 1]
  • 用尾随闭包书写

    上面的代码

    可以写成

    nums.sort(by: {
        (i1: Int, i2: Int) -> Bool in
        return i1 > i2
    })

    也可以等价于下面几种

    nums.sort(by: { i1, i2 in return i1 > i2 })
    nums.sort(by: { i1, i2 in i1 > i2 })
    nums.sort(by: { $0 > $1 })
    nums.sort(by: > )
    nums.sort() { $0 > $1 }
    nums.sort { $0 > $1 }

忽略参数

Swift中,很多时候,如果我们对于参数不做处理,可以用 下划线 _ 来代替

例如下面的闭包

func exec(fn: (Int, Int) -> Int) {
    print(fn(1, 2))
}
print(exec{_,_ in 100 })  // 100

自动闭包

自动闭包:

1.自动闭包顾名思义就是自动创建一个闭包用来包裹一个表达式,这种闭包不接受任何参数,当闭包被调用时,返回包裹在闭包中的表达式的值
2.自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行
3.这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包
  • 函数

    假设我们定义一个这样的函数,要求 如果第1个数大于0,返回第一个数。否则返回第2个数

    func getFirstPositive(_ v1: Int, _ v2:  Int) -> Int? {
        return v1 > 0 ? v1 : v2
    }
    //调用
    getFirstPositive(10, 20) // 10
    getFirstPositive(-2, 20) // 20
    getFirstPositive(0, -4) // -4

    现在假如说,我们这么传入的话

    func getNum() -> Int {
        // 这里每次都执行
        let a = 100
        let b = 200
        return a + b
    }
    
    func getFirstPositive2(_ v1: Int, _ v2:  Int) -> Int? {
        return v1 > 0 ? v1 : v2 
    }
    getFirstPositive2(10, getNum())
  • 改成函数类型的参数

    因为第一个参数已经是10 大于0了,第二个参数,也就是getNum() 根本没必要去执行,浪费性能,所以,有没有什么办法能做到,当第一个参数不满足时候,才去执行getNum()呢?答案是有的

    // 改成函数类型的参数,可以让v2延迟加载
    func getFirstPositive2(_ v1: Int, _ v2: () -> Int) -> Int? {
        // 这里判断 v1 > 0 不会调用 v2()
        return v1 > 0 ? v1 : v2()
    }
    
    getFirstPositive2(10, {
        // 第一个参数大于0的时候,这里不会执行
        let a = 100
        let b = 200
        return a + b
    })
  • 改进

    如果改成这样写就报错了

    // 改成函数类型的参数,可以让v2延迟加载
    func getFirstPositive2(_ v1: Int, _ v2: () -> Int) -> Int? {
      // 这里判断 v1 > 0 不会调用 v2()
        return v1 > 0 ? v1 : v2()
    }
    
    getFirstPositive2(10,  20) //报错 Cannot convert value of type 'Int' to expected argument type '() -> Int'

    因为需要的是() -> Int类型,给的是Int

    我们可以写成下面两种都可以

    / 改成函数类型的参数,可以让v2延迟加载
    func getFirstPositive2(_ v1: Int, _ v2: () -> Int) -> Int? {
        // 这里判断 v1 > 0 不会调用 v2()
        return v1 > 0 ? v1 : v2()
    }
    getFirstPositive2(10, {20})
    getFirstPositive2(10) {20} 
  • @autoclosure

    上面的也可以用自动闭包技术

    func getFirstPositive3(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
        return v1 > 0 ? v1 : v2()
    }
    //这里v2传的虽然是20,但是函数定义中v2为自动闭包,根据自动闭包的特性,系统在编译时就会自动创建无参数值的闭包函数包含20,传递给getFirstPostive3函数。
    getFirstPositive3(-4, 20) 
  • 需要的注意点:

    • 为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚:这个值会被推迟执行

    • @autoclosure 会自动将 20 封装成闭包 { 20 }

    • @autoclosure 只支持 () -> T 格式的参数 @autoclosure 并非只支持最后1个参数

    • 空合并运算符 ?? 使用了 @autoclosure 技术

    • 有@autoclosure、无@autoclosure,构成了函数重载

 逃逸闭包

逃逸闭包:

一个传入函数的闭包如果在函数执行结束之后才会被调用,那么这个闭包就叫做逃逸闭包。通俗点讲,不在当前方法中使用闭包,而是在方法之外使用

定义函数的参数为逃逸闭包时,只需要在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的

将一个闭包标记为 @escaping意味着你必须在闭包中显式地引用self

  • 示例:
    var result: ()->Void = {}
    var str = ""
    
    ///逃逸闭包
    func showA(closure: @escaping () -> Void) {
        result = closure
    }
    
    ///普通闭包
    func showB(closure: () -> Void) {
        closure()
    }
    
    func doSomething() {
        showA {str = "我是逃逸闭包"}
        showB {str = "我是普通闭包"}
    }
    
    doSomething()
    print(str)    //我是普通的闭包
    result()
    print(str)    //我是逃逸的闭包
  • 打印结果:
    我是一个普通的闭包
    我是一个逃逸闭包
  • 总结:
    逃逸闭包是在函数执行之后再执行,于是这段代码最后输出“我是逃逸的闭包”

posted on 2021-04-23 10:30  梁飞宇  阅读(192)  评论(0)    收藏  举报