Fu*king Closures

作者: Xtuphe | 来源:发表于2019-07-03 13:39 被阅读0次

    闭包能在其被定义的上下文中捕获和存储任何常量和变量的引用,这叫关闭这些常量和变量。

    • 全局函数是有名字且不捕获任何值的闭包
    • 嵌套函数是有名字且可从其函数捕获值的闭包
    • 闭包表达式是用轻便的语法写的没名字的闭包,可从周围上下文捕获值

    闭包有些特殊优化:

    • 自动推断参数与返回值的类型
    • 单行闭包隐性返回
    • 速写参数名
    • 尾部闭包语法

    排序方法

    Swift的标准库中提供一个sorted(by:),可以用来根据你提供的闭包的输出来对数组中元素进行排序。排序完成后该方法会返回一个新的排序后的数组,原数组没有被修改。

    sorted(by:)方法接收一个闭包,这个闭包要传2个与数组元素类型相同的参数,并会返回一个Bool,为true时,第一个值会在第二个值的前面,为false时则反之。

    例如下面,可以把普通的函数作为参数传过去

    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"]
    

    闭包表达式语法

    通常如下:

    { (parameters) -> return type in
        statements
    }
    

    所以上面的排序可以简写为:

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

    要注意的是这个闭包声明的参数与返回类型与 backward(::) 几乎一模一样,但,这是个包内表达式,参数与返回值是写在{}的里面。
    闭包的包体被in关键字区分开来,in关键字代表了此闭包的定义(即参数与返回类型)结束了,包体要开始了。

    从上下文中推断类型

    因为排序闭包是作为一个参数传递给一个方法的,Swift可以推断出其参数的类型与返回类型。sorted(by:) 被一个含String元素的数组调用,所以其参数类型一定为(String, String) -> Bool,这意味着这些可以省去不写了。

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

    单行闭包隐性返回

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

    由于sorted(by:)方法的参数明确了需要一个Bool来作为闭包的返回值,并且闭包的包体包含的唯一一行表达式(s1 > s2)返回Bool,没有歧义,所以return可被省略。

    速写参数名

    Swift自动提供速写参数名(0,1, $2......)来指代闭包的参数,如果你在闭包中使用速写参数名,那么你可以省略定义参数,而参数的数量和类型会从函数类型中被推断出,此时in关键字也可以被省略。

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

    运算符方法

    Swift的String类型定义了针对String类型的运算符>作为一个方法,此方法有两个String类型的参数,并返回Bool。刚好符合sorted(by:)需要的类型。因此,可以继续简写为:

    reversedNames = names.sorted(by: > )
    

    尾部闭包

    如果你需要把闭包作为一个函数的最后一个参数传递,且此闭包表达式很长,可以将其写为尾部闭包。一个尾部闭包,虽然是一个参数,可以写在方法()的后面。当你使用尾部闭包语法时,你不需要写参数标签。

    func someFunctionThatTakesAClosure(closure: () -> Void) {
        // function body goes here
    }
    
    // Here's how you call this function without using a trailing closure:
    someFunctionThatTakesAClosure(closure: {
        // closure's body goes here
    })
    
    // Here's how you call this function with a trailing closure instead:
    someFunctionThatTakesAClosure() {
        // trailing closure's body goes here
    }
    

    所以前面写的sorted(by:)也可以写为:

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

    如果一个闭包是这个方法的唯一参数,那不需要写()

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

    map(_:)

    Swift的Array类型有map(:) 方法,接收一个闭包最为其唯一参数。数组中的每个元素都会使map(:) 会被调用一次,并返回映射过的值(可以是其他类型)。映射的特性与返回值的类型则由闭包来实现。
    在闭包对每个元素执行过后,map(_:) 方法返回一个新的array,包含所有映射过的值,且对应的顺序与原数组相同。

    下面是如何把[16, 58, 510] 转换为新的数组["OneSix", "FiveEight", "FiveOneZero"]:

    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"]
    

    用numbers数组通过传map(:)作为尾部闭包来创建strings数组。map(:) 方法会为数组中的每个元素调用闭包。你不需指定闭包输入的参数类型,因为类型可以从数组的值中被推断出。

    在这个例子中,变量number用闭包的number参数来初始化,所以number可以被修改(函数和闭包的参数永远是常量)。闭包表达式也声明了一个返回类型String,来表明最终被存储到映射后数组的值类型。

    值捕获

    一个闭包可以从其定义的上下文中捕获常量或变量。然后闭包可在包体内引用或修改这些常量或变量,即使在原领域内这些常量或变量已不存在了。

    下面是一个叫makeIncrementer的函数,其包含一个叫incrementer的嵌套函数。incrementer()函数捕获2个值,runningTotal和amount,捕获这些值后,incrementer作为makeIncrementer的闭包返回,每次被调用时,runningTotal会增加amount。

    func makeIncrementer(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        func incrementer() -> Int {
            runningTotal += amount
            return runningTotal
        }
        return incrementer
    }
    

    makeIncrementer的返回类型是() -> Int。这意味着它返回一个函数,而不是简单的值。它还定义了一个变量runningTotal,来储存当前递增的总次数。

    如果将上面嵌套的incrementer()函数抽出来看,似乎有点点奇怪

    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    

    这个函数没有任何参数,然而却引用了runningTotal和amount。这是通过捕获他俩的引用来实现的。捕获引用确保了再makeIncrementer结束时,runningTotal和amount不会消失,并且在下一次incrementer被调用时runningTotal可用。

    let incrementByTen = makeIncrementer(forIncrement: 10)
    
    incrementByTen()
    // returns a value of 10
    incrementByTen()
    // returns a value of 20
    incrementByTen()
    // returns a value of 30
    

    如果你创建另外一个incrementer,他会有其自己新的,分开的runningTotal:

    let incrementBySeven = makeIncrementer(forIncrement: 7)
    incrementBySeven()
    // returns a value of 7
    incrementByTen()
    // returns a value of 40
    
    Note:
    如果把闭包赋值给一个类实例的属性,那么这个闭包会捕获此实例或其成员,造成循环引用。Swift用capture lists来打破循环引用。
    

    闭包是引用类型

    在上面的例子中,incrementBySeven和incrementByTen为常量,但是这些常量引用的闭包仍然能够递增其捕获的runningTotal变量。这是因为函数与闭包属于引用类型。

    每当你赋值一个函数或闭包给一个常量或变量,你是将常量或变量设置为函数或闭包的一个引用。上面例子中,incrementByTen常量引用的是要选择哪个闭包,而不是闭包的内容。
    这也意味着当你把一个闭包赋值给两个不同的常量或变量,他们均将引用这个相同的闭包。

    let alsoIncrementByTen = incrementByTen
    alsoIncrementByTen()
    // returns a value of 50
    
    incrementByTen()
    // returns a value of 60
    

    闭包逃逸

    当闭包被用作参数传递,并且在函数调用结束后被调用时, 被称作是逃逸。当你声明一个参数含有闭包的函数时,你可以在参数类型前写@escaping来表示此闭包允许逃逸。

    闭包逃逸的一种方式是将其存储在函数外的一个变量中。函数在其开始运行后返回,但闭包会直到运行结束后被调用。

    var completionHandlers: [() -> Void] = []
    func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
        completionHandlers.append(completionHandler)
    }
    

    someFunctionWithEscapingClosure(_:) 函数接收一个闭包作为参数并将其添加到一个在函数外声明的数组。如果你没有用@escape标记参数,会得到一个编译期错误。

    用@escape标记闭包意味着你需要在比包内显性引用self,例如在下面代码中,传递给someFunctionWithEscapingClosure(:)的闭包是一个逃逸闭包,意味着需要显性引用self,而someFunctionWithNonescapingClosure(:)不是逃逸闭包,意味着可以隐性引用self。

    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"
    

    相关文章

      网友评论

        本文标题:Fu*king Closures

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