第7章:闭包

作者: 行知路 | 来源:发表于2019-02-17 12:01 被阅读2次

      闭包是自包含的功能块,可以在代码中传递和使用。Swift中的闭包类似于Objective-C中的块以及其他编程语言中的lambdas。闭包也类似于C语言中的函数指针,但是其比函数指针强大。闭包定义它们的上下文中捕获和存储对任何常量和变量的引用,Swift为您处理捕获的所有内存管理。
      闭包有三种形式:

    • 全局函数是具有名称但不捕获任何值的闭包。
    • 嵌套函数是具有名称的闭包,可以从其封闭函数中捕获值。
    • Closure表达式是一种未命名的闭包,用轻量级语法编写,可以从周围的上下文中捕获值。

    7.1 闭包表达式

    全局函数与嵌套函数在前面章节已经讲述过,下面以数组的sorted(by:)方法为例讲述闭包表达式。

    通过提供函数来实现数组的排序,以下讲解中排序方法都是降序排列

    let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
    
    func backward(_ s1: String, _ s2: String) -> Bool {
        return s1 > s2
    }
    var reversedNames = names.sorted(by: backward)
    // reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    

      以下通过闭包表达式与上述通过函数调用的storted方法进行对比。

    闭包表达式的一般语法

    { (parameters) -> return type in
        statements
    }
    

    完整闭包表达式排序

    reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
        return s1 > s2
    })
    

    由于storted方法明确给出了参数的类型——即s1、s2两个变量的类型,所以参数部分可以类型部分可以交由Swift编译器进行推断,从而让我们省略参数类型

    reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
    

    由于storted方法明确给出了闭包的返回类型——返回结果是Bool,所以闭包返回类型部分可以交由Swift编译器进行推断,从而让我们省略闭包返回类型

    reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
    

    请注意只有在闭包是单个表达式的情况下才行,如果有多于一个表达式,会出现编译错误!

    除了以上优化外,Swift还提供了类似shell脚本中的参数速记方法

    // $0、$1、$2、$3......,分别指代第一个到第N个参数
    reversedNames = names.sorted(by: {$0 > $1 } )
    

    在Swift中String类型也定义了大于号操作符,该操作符的定义正好符合storted方法对闭包的要求,所以还可以简写到令人吃惊的地步

    reversedNames = names.sorted(by: > )
    

    7.2 尾闭包优化

      在很多情况下,闭包作为参数传递给方法或函数时,该参数是参数列表的最后一个参数,在此情况下存在优化的空间,以下是具体示例。

    // 此函数需要一个闭包参数
    func someFunctionThatTakesAClosure(closure: () -> Void) {
        // function body goes here
    }
    
    // 以下是不使用尾闭包优化的调用方法
    someFunctionThatTakesAClosure(closure: {
        // closure's body goes here
    })
    
    // 以下是使用尾闭包优化的调用方法
    someFunctionThatTakesAClosure() {
        // trailing closure's body goes here
    }
    
    // 如果该闭包参数是唯一的参数,那么可以进一步简化调用方法——省略小括号
    someFunctionThatTakesAClosure{
        // trailing closure's body goes here
    }
    

    下面是一个更真实有用的例子——把阿拉伯数字转换为英文单词组成的数字

    let digitNames = [
        0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
        5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
    ]
    let numbers = [16, 58, 510]
    
    let strings = numbers.map { (number) -> String in
        var number = number
        var output = ""
        repeat {
            output = digitNames[number % 10]! + output
            number /= 10
        } while number > 0
        return output
    }
    // strings is inferred to be of type [String]
    // its value is ["OneSix", "FiveEight", "FiveOneZero"]
    

    7.3 捕捉值

      与C语言中的函数指针相比,闭包的一大特点是可以捕捉定义其的上下文范围内的值。以下示例展示了捕捉值以及一些细节。

    // 定义了一个函数,这个函数有个嵌套函数(实际上就是个闭包),并返回该函数
    // 该嵌套函数捕捉了runningTotal、amount两个值
    func makeIncrementer(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        func incrementer() -> Int {
            runningTotal += amount
            return runningTotal
        }
        return incrementer
    }
    
    // 获得该嵌套函数
    let incrementByTen = makeIncrementer(forIncrement: 10)
    
    // 调用该函数
    incrementByTen()
    // returns a value of 10
    incrementByTen()
    // returns a value of 20
    incrementByTen()
    // returns a value of 30
    
    // 获取另外一个嵌套函数
    let incrementBySeven = makeIncrementer(forIncrement: 7)
    incrementBySeven()
    // returns a value of 7
    

    通过以上示例可以看到incrementBySeven、incrementByTen两个常量捕捉的runningTotal、amount值是不同的;
    虽然incrementBySeven、incrementByTen是常量,但是其仍然可以修改runningTotal、amount的值,这说明闭包其实是引用类型(后续章节会讲到值类型与引用类型)

    7.4 逃逸闭包与非逃逸闭包

      逃逸闭包与非逃逸闭包是仅针对作为函数参数的闭包而言,其他情况下的闭包没有这个属性。

    • @escaping 逃逸闭包
    • @noescaping 非逃逸闭包——默认值

    逃逸闭包
    在函数执行完毕后,闭包再执行的闭包就是逃逸闭包,其与函数的执行是异步的。

    非逃逸闭包
    与函数同步执行的闭包是非逃逸闭包。

    var completionHandlers: [() -> Void] = []
    func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
        completionHandlers.append(completionHandler)
    }
    
    func someFunctionWithNonescapingClosure(closure: () -> Void) {
        closure()
    }
    
    class SomeClass {
        var x = 10
        func doSomething() {
            someFunctionWithEscapingClosure { self.x = 100 }
            someFunctionWithNonescapingClosure { x = 200 }
        }
    }
    
    let instance = SomeClass()
    instance.doSomething()
    print(instance.x)
    // Prints "200"
    
    completionHandlers.first?()
    print(instance.x)
    // Prints "100"
    

    7.5 自动闭包

      顾名思义,自动闭包是由Swift编译器为我们自动创建的闭包,其主要作用是简化代码。

    • 自动闭包没有参数
    • 自动闭包会返回表达式的值

    自动闭包示例如下

    var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
    print(customersInLine.count)
    // Prints "5"
    
    let customerProvider = { customersInLine.remove(at: 0) }
    print(customersInLine.count)
    // Prints "5"
    
    // customerProvider所代表的闭包没有声明返回值,但是由于remove会返回值,所以该闭包最终被推断为有返回值
    print("Now serving \(customerProvider())!")
    // Prints "Now serving Chris!"
    print(customersInLine.count)
    // Prints "4"
    

    相关文章

      网友评论

        本文标题:第7章:闭包

        本文链接:https://www.haomeiwen.com/subject/xuqgeqtx.html