美文网首页Swift
Swift 3.0之七、闭包

Swift 3.0之七、闭包

作者: 麟young | 来源:发表于2016-12-12 11:34 被阅读54次

    闭包能够捕获和存储其上下文中的常量和变量的引用,闭合并包裹这些常量和变量,因此被称为“闭包”。

    函数是特殊形式的闭包,常见的闭包形式为:

    • 全局函数: 有名字但不会捕获任何值的闭包
    • 内嵌函数: 有名字且能从其上层函数捕获值的闭包
    • 闭包表达式: 是一个轻量级语法写的、可以捕获其上下文中常量或变量值的没有名字的闭包。(匿名函数)

    1. 闭包表达式

    Sorted 方法

    Swift 的标准库提供了一个叫做sorted(by:)的方法为已知类型的数组元素进行排序,参数为“两个数组元素类型的形参和返回值为Bool类型”的闭包。一旦排序完成,sorted(by:)方法会返回与原数组类型大小相同、排序好的数组,原始数组不会被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 的值为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    

    闭包表达式语法

    闭包表达式语法的一般形式:

     { (参数1: 类型,参数2: 类型,...) -> (返回类型) in
        // 要实现的内容
     }
    

    举个🌰 ,将上面的栗子简化为闭包表达式写法就是:

    reversedNames = names.sorted(by: 
    { (s1: String, s2: String) -> Bool in // in 关键字表示函数声明部分完成,后面即函数体。
        return s1 > s2
    })
    

    从语境中推断类型

    sorted(by:)的参数为闭包,且Swift能推断它为(String, String) -> Bool类型。这意味着 (String, String)Bool可以省略,包括返回箭头(->)和形式参数名外面的括号也可被省略:

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

    从单表达式闭包隐式返回

    单表达式闭包可以省略return关键字:

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

    简写的实际参数名

    Swift 自动对行内闭包提供简写实际参数名,可以通过$0$1$2等名字来引用闭包的实际参数值。
    如果在闭包表达式中使用这些简写实际参数名,那么可以忽略实际参数列表的书写,in关键字也能被省略:

    reversedNames = names.sorted(by: { $0 > $1 } )
    

    运算符函数

    由于String类型定义了大于号(>)的实现就是(String, String) -> Bool类型,因此:

    reversedNames = names.sorted(by: >)
    

    2. 尾随闭包

    闭包表达式作为函数最后一个实际参数传递给函数时,可以写在小括号后面,如上面的栗子:

    reversedNames = names.sorted(by: { $0 > $1 } )
    

    采用尾随闭包的写法为:

    reversedNames = names.sorted() { $0 > $1 }
    

    如果闭包是唯一的参数,采用尾随闭包的写法时,可以省略括号,即:

    reversedNames = names.sorted { $0 > $1 }
    

    当闭包很长时,采用尾随闭包的写法显得非常美观。

    3. 捕获值

    一个闭包能够从上下文捕获已被定义的常量和变量,即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值,举一个内嵌函数的🌰 :

    //  这是一个根据传入数值返回“增加器”函数的栗子
    // 其中,内嵌函数incrementer()对外部的runningTotal变量进行了修改
    func makeIncrementer(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        func incrementer() -> Int {
            runningTotal += amount
            return runningTotal
        }
        return incrementer
    }
    

    接下来,使用这个函数:

    // 首先,创建一个“加10器”函数
    let incrementByTen = makeIncrementer(forIncrement: 10)
    // 调用这个函数
    incrementByTen()
    // 返回 10
    incrementByTen()
    // 返回 20
    incrementByTen()
    // 返回 30
    
    // 创建另外一个“加7器”函数
    let incrementBySeven = makeIncrementer(forIncrement: 7)
    incrementBySeven()
    // 返回 7
    
    // 再次调用“加10器”函数
    incremByTen()
    // 返回 40
    

    4. 闭包是引用类型

    在上面例子中, incrementBySevenincrementByTen 是常量,但是这些常量指向的闭包仍可以增加已捕获的变量 runningTotal 的值,这是因为函数和闭包都是引用类型。
    这意味着赋值一个闭包到两个不同的常量或变量中,这两个常量或变量都指向相同的闭包:

    let alsoIncrementByTen = incrementByTen
    alsoIncrementByTen()
    // 返回 50
    

    5. 逃逸闭包

    在函数return之前调用闭包称之为不可逃逸闭包,用@noescape关键字来明确闭包不允许逃逸。
    在函数return之后再调用闭包称之为可逃逸闭包,用@escaping关键字来明确闭包允许逃逸。
    (不加关键字的情况,默认执行的是非逃逸闭包)。
    举个逃逸闭包的🌰 :

    // 首先,创建一个数组,数组内的元素都是“() -> Void”类型的函数
    var completionHandlers: [() -> Void] = []
    // 这个函数用到逃逸闭包 用来给上面的数组添加函数
    func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
        completionHandlers.append(completionHandler) 
    } // 如果不使用@escaping关键字,会遇到编译时错误。
    // 这个函数为非逃逸闭包
    func someFunctionWithNonescapingClosure(closure: () -> Void) {
        closure()
    }
     
    class SomeClass {
        var x = 10
        func doSomething() {
            someFunctionWithEscapingClosure { self.x = 100 } // 逃逸闭包在访问变量时,要显式地使用self关键字。
            someFunctionWithNonescapingClosure { x = 200 }   // 非逃逸闭包在访问变量时,可以省略self关键字。
        }
    }
     
    let instance = SomeClass()
    instance.doSomething()
    print(instance.x)
    // 结果为: "200"
     
    completionHandlers.first?()
    print(instance.x)
    // 结果为: "100"
    

    6. 自动闭包

    自动闭包有两个特点: 不接受任何形参,即() -> T类型; 不调用不执行。写法为:

    var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
    print(customersInLine.count)
    // 结果为: "5"
     
    let customerProvider = { customersInLine.removeAtIndex(0) } // 创建自动闭包并赋值给customerProvider常量,但闭包内的语句并未执行。
    print(customersInLine.count)
    // 结果为: "5"
     
    print("Now serving \(customerProvider())!")  // 直到调用customerProvider()时,闭包内的语句真正执行。
    // 结果为: "Now serving Chris!"
    print(customersInLine.count)
    // 结果为: "4"
    

    通过@autoclosure关键字标记形式参数使用了自动闭包,这样,在调用时可以省略闭包外面的大括号,如:

    // customersInLine 现在为 ["Ewa", "Barry", "Daniella"]
    func serve(customer customerProvider: @autoclosure () -> String) {
        print("Now serving \(customerProvider())!")
    }
    serve(customer: customersInLine.remove(at: 0)) 
    // 因为上面使用了@autoclosure关键字,相应的参数会自动转换为闭包的形式传递,
    // 所以这里调用时可以将“{ customersInLine.remove(at: 0) }”的大括号省略。
    // 结果为: "Now serving Ewa!"
    

    如果要自动闭包允许逃逸,就同时使用 @autoclosure@escaping 关键字:

    // customersInLine 现在为 ["Barry", "Daniella"]
    // 创建元素类型为“() -> String”的数组
    var customerProviders: [() -> String] = []
    // 这个函数的参数用到了自动闭包 并允许逃逸
    func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
        customerProviders.append(customerProvider)
    }
    collectCustomerProviders(customersInLine.remove(at: 0))
    collectCustomerProviders(customersInLine.remove(at: 0))
     
    print("Collected \(customerProviders.count) closures.")
    // 结果为: "Collected 2 closures."
    for customerProvider in customerProviders {
        print("Now serving \(customerProvider())!")
    }
    // 结果为: "Now serving Barry!"
    // 结果为: "Now serving Daniella!"
    

    相关文章

      网友评论

        本文标题:Swift 3.0之七、闭包

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