Swift 闭包

概念

  • 是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。

  • 相对于Objective-C 有如下优化点

    • 利用上下文推断参数和返回值类型
    • 隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字
    • 参数名称缩写
    • 尾随闭包语法

闭包表达式

  • 语法
    { (parameters) -> returnType in
          statements
    }
  • 注意点:
    • 可以是 in-out 参数(Swift参数默认不能修改,但是使用关键字inout修饰之后,就可以修改,这样的参数,叫做in-out参数),但不能设定默认值
    • 也可以使用具名的可变参数(用…修饰)(注:但是如果可变参数不放在参数列表的最后一位的话,调用闭包的时时编译器将报错。)
    • func add(a:Int, b:Int ,others:Int ...) -> Int {
      var result = a + b
      for num in others {
      result += num
      }
      return result
      }
      let number = add(2, b: 5, others: 2, 50, 4)
      print(number)
      • 元组也可以作为参数和返回值
      • Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0,$1,$2 来顺序调用闭包的参数,以此类推。
        reversedNames = names.sorted(by: >)

尾随闭包

  • 概念:尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签。
  • func someFunctionThatTakesAClosure(closure: () -> Void) {
      // 函数体部分
    }
    // 以下是不使用尾随闭包进行函数调用
    someFunctionThatTakesAClosure(closure: {
      // 闭包主体部分
    })
    // 以下是使用尾随闭包进行函数调用
    someFunctionThatTakesAClosure() {
      // 闭包主体部分
    }
  • 注意:如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉:
    reversedNames = names.sorted { $0 > $1 }

值捕获

  • 概念:闭包可以在其被定义的上下文中捕获常量或变量,即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
  • 原因:函数和闭包都是引用类型。无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。

逃逸闭包

  • 概念:当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
  • 逃逸闭包就是把这个传入的block可以保存在外部.等待某一时刻你可以再次调用这个block.这一个时刻是在函数返回之后.*
//一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。
//这里是一个闭包数组
typealias completionHandlerBlock = () -> Void
var completionHandlers: [completionHandlerBlock] = []
class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWith_Escaping_Closure { self.x = 100 }  //将一个闭包标记为 @escaping 意味着你必须在闭包中显式地引用 self。
        someFunctionWith_Non_Escaping_Closure { x = 200 }   //不带self
    }
    //逃逸闭包
    func someFunctionWith_Escaping_Closure(completionHandler: @escaping completionHandlerBlock) {
        //函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你不将这个参数标记为 @escaping,就会得到一个编译错误。
        completionHandlers.append(completionHandler)
    }

    //非逃逸闭包
    func someFunctionWith_Non_Escaping_Closure(closure: () -> Void) {
        //该闭包是一个非逃逸闭包,这意味着它可以隐式引用 self。同时无法保存在外部
        completionHandlers.append(closure)  //报错 : Passing non-escaping parameter 'closure' to function expecting an @escaping closure
        closure()
    }
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出 "200"

completionHandlers.first?()
print(instance.x)
// 打印出 "100"

自动闭包

  • 概念:autoclosure 做的事情就是把⼀句表达式⾃动地封装成⼀个闭包 *(closure),使用 *@autoclosure来进行修饰。
    func logIfTrue(_ predicate:@autoclosure () -> Bool) {
          if predicate() {
              print("true")
          }
      }
      //调用的时候可以直接写:
      logIfTrue(2>1)