zoukankan      html  css  js  c++  java
  • Swift 闭包

    前言

    • 闭包是一个自包含的功能性代码模块。

      • 一段程序代码通常由常量、变量和表达式组成,然后使用一对花括号 “{}” 来表示闭合并包裹着这些代码,由这对花括号包裹着的代码块就是一个闭包。
      • 通俗的解释就是一个 Int 类型里存储着一个整数,一个 String 类型包含着一串字符,同样,闭包是一个包含着函数的类型。
      • Swift 中的闭包与 C 和 OC 中的 Block 以及其他一些编程语言中的 lambdas 比较相似。Block 和闭包的区别只是语法的不同而已,而且闭包的可读性比较强。
    • 在 Swift 中闭包有着非常广泛的应用,有了闭包,你就可以处理很多在一些古老的语言中不能处理的事情,这是因为闭包使用的多样性。

      • 比如你可以将闭包赋值给一个变量。
      • 你也可以将闭包作为一个函数的参数。
      • 你甚至可以将闭包作为一个函数的返回值。
      • 闭包可以捕获和存储其所在上下文中任意常量和变量的引用,并且 Swift 会为你管理在捕获过程中涉及的所有内存操作。
    • 闭包是引用类型的。

      • 无论你将函数/闭包赋值给一个常量还是变量,实际上都是在将常量/变量设置为对应函数/闭包的引用,这也意味着如果你将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包。
      • 在使用闭包时需要注意循环引用。
    • 从本质上来说,函数、方法和闭包是一体的,闭包的功能类似于函数嵌套,但是闭包更加灵活,形式更加简单。在 Swift 语言中有三种闭包形式。

      • 全局函数:是一个有名字但不会捕获任何值的闭包。
      • 嵌套函数:是一个有名字并可以捕获到其封闭函数域内的值的闭包。
      • 匿名闭包:闭包表达式是一个利用轻量级语法所写的,可以捕获其上下文中变量或常量值。

    1、闭包的形式

    1.1 函数形式闭包

    • 函数形式闭包示例

      let namesArray: Array = ["Jill", "Tim", "Chris"]
      
      func myConpare(s1: String, s2: String) -> Bool {        
          return s1 > s2
      }
      
      let names = namesArray.sort(myConpare)
      

    1.2 一般形式闭包

    • 一段程序代码通常由常量、变量和表达式组成,然后使用一对花括号 “{}” 来表示闭合并包裹着这些代码,由这对花括号包裹着的代码块就是一个闭包。

      { (参数名1: 参数类型, 参数名2: 参数类型, ...) -> 返回值类型 in
          
          语句组
      }
      
      • 闭包写在一对大括号 {} 中,用 in 关键字分割。
      • in 后的语句是闭包的主体。
      • in 之前的参数和返回值类型是 “语句组” 中所使用的参数和返回值格式的一种指示,并不必在语句组中进行逻辑运算与返回。
      • 可以使用常量、变量、inout、可变参数、元组类型作为闭包的参数,但不能在闭包参数中设置默认值,定义返回值和函数返回值的类型相同。
      • 闭包表达式的运算结果是一种函数类型,可以作为表达式、函数参数和函数返回值。
    • 一般形式闭包示例

      let namesArray: Array = ["Jill", "Tim", "Chris"]
          
      let names = namesArray.sort { (s1: String, s2: String) -> Bool in
          
          return s1 > s2
      }
      

    1.3 参数类型隐藏形式闭包

    • Swift 中有类型推断的特性,可以根据上下文推断出参数类型,所以我们可以去掉参数类型。

      { (参数名1, 参数名2, ...) -> 返回值类型 in
      
          语句组
      }
      
    • 参数类型隐藏形式闭包示例

      let namesArray: Array = ["Jill", "Tim", "Chris"]
          
      let names = namesArray.sort { (s1, s2) -> Bool in
              
          return s1 > s2
      }
      

    1.4 返回值类型隐藏形式闭包

    • Swift 中有类型推断的特性,可以根据上下文推断出返回值类型,所以我们可以去掉返回值类型。

      { (参数名1, 参数名2, ...) in
      
          语句组
      }
      
    • 返回值类型隐藏形式闭包示例

      let namesArray: Array = ["Jill", "Tim", "Chris"]
          
      let names = namesArray.sort { (s1, s2) in
              
          return s1 > s2
      }
      

    1.5 return 隐藏形式闭包

    • 如果在闭包中只有一条语句,比如示例中的 return s1 > s2,那么这种语句只能是返回语句,此时关键字 return 可以省略,省略后的格式变为一种隐式返回。

      { (参数名1, 参数名2, ...) in
      
          语句组(省略 return)
      }
      
    • return 隐藏形式闭包

      let namesArray: Array = ["Jill", "Tim", "Chris"]
          
      let names = namesArray.sort { (s1, s2) in
              
          s1 > s2
      }
      

    1.6 参数名省略形式闭包

    • 闭包的使用非常的灵活,我们可以省略闭包参数列表中的参数的参数类型定义,被省略的参数类型会通过闭包函数的类型进行推断。

    • 同时,我们也可以在闭包函数体中通过使用闭包的参数名简写功能,直接使用 $0$1$2 等名字就可以引用闭包的参数值,$0 指第一个参数,$1 指第二个参数, Swift 能够根据闭包中使用的参数个数推断出参数列表的定义。

    • 如果同时省略了参数名和参数类型,那么 in 关键字也必须被省略,此时闭包表达式完全由闭包函数体构成。

      {
          语句组(使用 $0、$1、$2)
      }
      
    • 参数名省略形式闭包示例

      let namesArray: Array = ["Jill", "Tim", "Chris"]
          
      let names = namesArray.sort {
              
          $0 > $1
      }
      

    1.7 trailing 形式闭包

    • 闭包可以做其他函数的参数,而且通常都是函数的最后一个参数。但是如果作为参数的这个闭包表达式非常长,那么很有可能会影响函数调用表达式的可读性,这个时候我们就应该使用 trailing 闭包。

    • trailing 闭包和普通闭包的不同之处在于它是一个书写在函数参数括号之外(之后)的闭包表达式,函数会自动将其作为最后一个参数调用。

    • 当函数有且仅有一个参数,并该参数是闭包时,不但可以将闭包写在 () 外,还可以省略 ()。

      exampleFunction(para1, para2) {
          
          语句组(使用 $0、$1、$2)
      }
      
    • trailing 形式闭包示例

      let namesArray: Array = ["Jill", "Tim", "Chris"]
          
      let names = namesArray.sort() {
              
          $0 > $1
      }
      

    2、闭包捕获

    • 闭包可以在其定义的上下文中捕获常量或变量,即使定义这些常量或变量的原作用域已经不存在,仍然可以在闭包函数体内引用和修改这些常量或变量,这种机制被称为闭包捕获。

    • 比如:嵌套函数就可以捕获其父函数的参数以及定义的常量和变量,全局函数可以捕获其上下文中的常量或变量。

      func increment(amount: Int) -> (() -> Int) {
                      
          var total = 0
                      
          func incrementAmount() -> Int {
                          
              // total 是外部函数体内的变量,这里是可以捕获到的
              total += amount
                          
              return total
          }
      			
          // 返回的是一个嵌套函数(闭包)
          return incrementAmount
      }
        
      // 闭包是引用类型,所以 incrementByTen 声明为常量也可以修改 total
      let incrementByTen = increment(10)
      incrementByTen()    // return 10,incrementByTen 是一个闭包
      		    
      // 这里是没有改变对 increment 的引用,所以会保存之前的值
      incrementByTen()    // return 20
      incrementByTen()    // return 30
      			
      let incrementByOne = increment(1)
      incrementByOne()    // return 1,incrementByOne 是一个闭包
      incrementByOne()    // return 2
      incrementByTen()    // return 40
      incrementByOne()    // return 3
      

    3、闭包捕获列表

    • Swift 中可以显式地指定闭包的捕获列表。

    • 捕获列表需要尾随 in 关键字,并且紧跟着参数列表。

      { [unowned self] (a:A, b:B) -> ReturnType in
          ...
      }
      
    • 通过标记 selfunowned 或者用 weak 来打破循环引用,这种方法常常用来修改闭包捕获的这个 self 的属性,苹果官方语言指南要求如果闭包和其捕获的对象相互引用,应该使用 unowned,这样能够保证他们会同时被销毁,这大概是为了避免对象被释放后维护 weak 引用空指针的开销。

      { [unowned self] in 
          ...
      }
      
    • 捕获列表除了可以设置 self,还可以单独声明引用类型成员变量,这避免了引用 thing1thing2 时污染周围代码。

      { [thing1 = self.grabThing(), weak thing2 = self.something] in
          ...
      }
      

    4、闭包循环引用

    5、闭包常用关键字

    5.1 @escaping 关键字

    • 用关键字 @noescape 修饰的闭包称为非逃逸闭包,而用 @escaping 修饰的闭包称为逃逸闭包。

      • 逃逸闭包,表示此闭包还可以被其他闭包调用,比如我们常用的异步操作。
      • 非逃逸闭包,传入闭包参数的调用限制在调用的函数体内,对性能有一定的提升,同时将使你能在闭包中隐式地引用 self
    • 在 Swift 标准库中很多方法,都用了 @noescape 属性,比如 Array 对应的方法 mapfilterreduce

      func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
      
      func filter(@noescape includeElement: (Self.Generator.Element) -> Bool) -> [Self.Generator.Element]
      
      func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) -> T) -> T
      
    • 在方法调用时,方法列表中传入的参数会被拷贝(值类型拷贝内部属性,引用类型拷贝指针),在方法体中操作的实际是拷贝的版本,在方法调用结束时,这些拷贝的版本也会被销毁。而闭包类型的参数和其它类型的参数不太相同。

      • 在 Swift 2.2 版本中

        • 闭包类型的参数默认会在方法返回后被返回,也就是说你可以在外部保存那些方法参数中的闭包,通常把这种特性称为 “escape” 延迟调用。
        • 如果你不需要闭包的 “escape” 特性,只想把闭包作为一段从外部写入的灵活代码,则可以在闭包参数名后加上 @noescape 关键字,这样闭包的代码就和其它参数一样,在方法返回前返回。
        • noescape 的参数不能被传递到方法外部。
        • 使用 @noescape 的用法是为方法提供灵活的外部代码,并且 @noescape 的闭包不会产生循环引用,所以调用本类型中的属性时不需要加 self
      • 在 Swift 3.0 中

        • 闭包延迟调用的默认状态发生了反转,noescape 成为了闭包的默认状态,@noescape 修饰符已经被删除了。
        • 现在如果你想使用 “escape” 特性闭包的话,则需要使用 @escaping 显示的声明。
        • 虽然 3.0 中的闭包做了修改,不过在 Swift 2.2 中数组中常用的 map、filter 等方法还都接受 @noescape 闭包参数。
    • 示例

      • 在 Swift 2.2 版本中

        struct ClouseTest {
            
            var num = 0
            
            var handlerCache: [() -> Void] = []
            
            // escaping 型的闭包,默认
            
            mutating func methodWithClouse(addedNum: Int, completeHandler: () -> Void) {
                
                num += addedNum
                
                // 把闭包参数加入到缓存数组中,未在方法中执行
                handlerCache.append(completeHandler)
            }
            
            // noescape 型的闭包,@ noescape 修饰
            
            func useNum(completeHandler: @noescape (Int) -> Int) -> Int {
                
                print(num)
                
                return completeHandler(num)
            }
        }
        
        var ct = ClouseTest()
        ct.methodWithClouse(addedNum: 5) {
            ct.num += 10
        }
        print(ct.num)               // num 为 5, 闭包中的代码是延迟调用的,在调用前不会执行
        
        // escaping 型的闭包,默认
        
        ct.handlerCache.first!()    // 调用缓存的闭包参数
        print(ct.num)               // num 的值变为 15
        
        // noescape 型的闭包
        
        let handleReuslt = ct.useNum { num in
            num + 10
        }
        print(handleReuslt)         // 25
        
      • 在 Swift 3.0 中

        struct ClouseTest {
            
            var num = 0
            
            var handlerCache: [() -> Void] = []
            
            // escaping 型的闭包,@escaping 修饰
            
            mutating func methodWithClouse(addedNum: Int, completeHandler: @escaping () -> Void) {
                
                num += addedNum
                
                // 把闭包参数加入到缓存数组中,未在方法中执行
                handlerCache.append(completeHandler)
            }
            
            // noescape 型的闭包,默认
            
            func useNum(completeHandler: (Int) -> Int) -> Int {
                
                print(num)
                
                return completeHandler(num)
            }
        }
        
        var ct = ClouseTest()
        ct.methodWithClouse(addedNum: 5) {
            ct.num += 10
        }
        print(ct.num)               // num 为 5, 闭包中的代码是延迟调用的,在调用前不会执行
        
        // escaping 型的闭包
        
        ct.handlerCache.first!()    // 调用缓存的闭包参数
        print(ct.num)               // num 的值变为 15
        
        // noescape 型的闭包,默认
        
        let handleReuslt = ct.useNum { num in
            num + 10
        }
        print(handleReuslt)         // 25
        

    5.2 @autoclosure 关键字

    • 用关键字 @autoclosure 修饰的闭包称为自动闭包。

      • 自动闭包,顾名思义是一种自动创建的闭包,用于包装函数参数的表达式,可以说是一种简便语法.
      • 自动闭包不接受任何参数,被调用时会返回被包装在其中的表达式的值。
      • 自动闭包的好处是让你能够延迟求值,因为代码段不会被执行直到你调用这个闭包,这样你就可以控制代码什么时候执行。
      • 含有 autoclosure 特性的声明同时也具有 noescape 的特性,即默认是非逃逸闭包,除非传递可选参数 escaping。如果传递了该参数,那么将可以在闭包之外进行操作闭包,形式为 @autoclosure(escaping)
    • 下面一起来看一个简单例子,比如我们有一个方法接受一个闭包,当闭包执行的结果为 true 的时候进行打印。

      func printIfTrue(predicate: ()-> Bool) {
          
          if predicate() {
              print("the result is true")
          }
      }
      
      // 直接调用方法
      printIfTrue { () -> Bool in
          return 2 > 1
      }
      
      // 闭包在圆括号内
      printIfTrue(predicate: {
          return 2 > 1
      })
      
      // 使用尾部闭包方式,闭包体在圆括号之外
      printIfTrue() {
          return 2 > 1
      }
      
      // 省略 return
      printIfTrue(predicate: {
          2 > 1
      })
      
      // 使用尾随闭包
      printIfTrue {
          2 > 1
      }
      
    • 但是不管哪种方式,表达上不太清晰,看起来不舒服,于是 @autoclosure 就登场了,我们可以在参数名前面加上 @autoclosure 关键字,这样我们就得到了一个写法简单,表意清楚的式子,被 @autoclosure 标注的闭包不再需要写在 "{ }" 中。

      func printIfTrue(predicate: @autoclosure () -> Bool) {
          
          if predicate() {
              print("the result is true")
          }
      }
      
      // 直接进行调用,Swift 将会把 2 > 1 这个表达式自动转换为 () -> Bool。
      printIfTrue(predicate: 2 > 1)
      
    • 如果有多个闭包,那么就有优势了,而 @autoclosure 是可以修饰任何位置的参数。

      func printInformation(predicate1: @autoclosure () -> Bool, predicate2: @autoclosure () -> Bool) {
          
          if predicate1() && predicate2() {
              print("the result is true")
          } else {
              print("the result is false")
          }
      }
      
      printInformation( predicate1: 3 > 2, predicate2: 4 > 1)
      

    6、闭包风格

    • 在 Swift 3.0 之前定义闭包可以用多种格式来表示。比如

      // 定义方式 1
      let plus: (Int, Int) -> Int = { x in 
          return x.0 + x.1
      }
      
      print(plus(1, 2))
      
      // 定义方式 2
      let plus: (Int, Int) -> Int = {x, y in
          return x + y
      }
      
      print(plus(1, 2))
      
      • 这两种格式的运算结果相同
      • 第一种闭包体中只声明了一个参数,这个参数需要与上下文中的格式相对应,所以 x 代表了一个元组 (Int, Int)
      • 第二种闭包体中传入了两个参数,所以分别对应了上下文中的两个 Int` 类型。
    • 实际上这种语法示存在歧义的,Swift 3.0 修复了这种歧义,使用更加严格的方式来响应上下文,参数的表达方式只有一种,即参数列表最外层括号内部的类型。

      // 定义方式 1
      let plus: ((Int, Int)) -> Int = { x in
          return x.0 + x.1
      }
      
      print(plus(1, 2))
      
      // 定义方式 2
      let plus: (Int, Int) -> Int = {x, y in
          return x + y
      }
      
      print(plus(1, 2))
      
  • 相关阅读:
    为什么Redis比Memcached易
    请注意CSDN社区微通道,许多其他的精彩等着你
    采用ACE登录设施(一)HelloWorld
    AIX 7.1 install python
    spring mvc入门
    iOS开展——全球应对MotionEvent
    2015第35周日
    2015第35周六转相见恨晚的知识列表
    2015第35周五JavaScript变量
    2015第35周四
  • 原文地址:https://www.cnblogs.com/QianChia/p/8625992.html
Copyright © 2011-2022 走看看