zoukankan      html  css  js  c++  java
  • Swift 中的闭包与 C 和 Objective-C中的 blocks 以及其它一些编程语言中的 lambdas 比較类似。

    闭包是功能性自包括模块,能够在代码中被传递和使用。 Swift 中的闭包与 C 和 Objective-C中的 blocks 以及其它一些编程语言中的 lambdas 比較相似。


     闭包能够 捕获 和存储其所在上下文中随意常量和变量的引用。

    这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift会为您管理在 捕获 过程中涉及到的内存操作。
    注意:假设您不熟悉 捕获 (capturing) 这个概念也不用操心。后面会具体对其进行介绍。
    在Swift函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包。闭包採取例如以下三种形式之中的一个:
    全局函数是一个有名字但不会捕获不论什么值的闭包
    嵌套函数是一个有名字并能够捕获其封闭函数域内值的闭包
    闭包表达式是一个利用轻量级语法所写的能够捕获其上下文中变量或常量值的没有名字的闭包


     Swift的闭包表达式拥有简洁的风格,并鼓舞在常见场景中以实现语法优化。主要优化例如以下:
    利用上下文判断參数和返回值类型
    单表达式(single-expression)闭包能够省略 return keyword
    參数名称简写
    Trailing 闭包语法
    闭包表达式嵌套函数是一种在较复杂函数中方便进行命名和定义自包括代码模块的方式。 当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是非常实用处的,尤其是在处理一些函数并须要将另外一些函数作为该函数的參数时。




     闭包表达式是一种利用简洁语法构建内联闭包的方式。 闭包表达式提供了一些语法优化。使得撰写闭包变得简单明了。

    以下闭包表达式的样例通过使用几次迭代展示了 sort 函数定义和语法优化的方式。 每一次迭代都用更简洁的方式描写叙述了同样的功能。


    sort 函数Swift 标准库提供了 sort 函数,会依据您提供的排序闭包将已知类型数组中的值进行排序。

    一旦排序完毕,函数会返回一个与原数组大小同样的新数组,该数组中包括已经正确排序的同类型元素。


     以下的闭包表达式演示样例使用 sort 函数对一个 String 类型的数组进行字母逆序排序,以下是初始数组值:
    let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] 




    排序函数有两个參数:
    已知类型值的数组。


    一个闭包,採用同样类型的数组的内容的两个參数,并返回一个布尔值来表示是否将第一个值在排序时放到第二个值的前面或是后面。假设第一个值应该出现第二个值之前,闭包须要返回true,否则返回false。




     该样例对一个 String 类型的数组进行排序,因此排序闭包需为 (String, String) -> Bool 类型的函数。




     提供排序闭包的一种方式是撰写一个符合其类型要求的普通函数,并将其作为 sort 函数的第二个參数传入:
    func backwards(s1: String, s2: String) -> Bool {
        return s1 > s2
    }
    var reversed = sort(names, backwards)
    // reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"] 




    假设第一个字符串 (s1) 大于第二个字符串 (s2)。backwards 函数则返回 true,表示在新的数组中 s1 应该出如今 s2 前。 字符中的 "大于" 表示 "依照字母顺序后出现"。 这意味着字母 "B" 大于字母 "A", 字符串 "Tom" 大于字符串 "Tim"。 其将进行字母逆序排序。"Barry" 将会排在 "Alex" 之后,一次类推。


     然而,这是一个相当冗长的方式,本质上仅仅是写了一个单表达式函数 (a > b)。 在以下的样例中,利用闭合表达式语法能够更好的构造一个内联排序闭包。
    闭包表达式语法闭包表达式语法有例如以下一般形式:
    { (parameters) -> returnType in
        statements





    闭包表达式语法能够使用常量、变量和 inout 类型作为參数,但不提供默认值。 也能够在參数列表的最后使用可变參数。元组也能够作为參数和返回值。




     以下的样例展示了之前 backwards 函数相应的闭包表达式版本号的代码:
    reversed = sort(names, { (s1: String, s2: String) -> Bool in
        return s1 > s2
    }) 




    须要注意的是内联闭包參数和返回值类型声明与 backwards 函数类型声明同样。 在这两种方式中,都写成了 (s1: String, s2: String) -> Bool类型。 然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。


     闭包的函数体部分由keyword in 引入。 该keyword表示闭包的參数和返回值类型定义已经完毕,闭包函数体即将開始。




     由于这个闭包的函数体部分如此短以至于能够将其改写成一行代码:
    reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } ) 




    这说明 sort 函数的总体调用保持不变。一对圆括号仍然包裹住了函数中整个參数集合。

    而当中一个參数如今变成了内联闭包 (相比于 backwards 版本号的代码)。


    依据上下文判断类型由于排序闭包是作为函数的參数进行传入的,Swift能够判断其參数和返回值的类型。 sort 期望第二个參数是类型为 (String, String) -> Bool 的函数,因此实际上 String, String 和 Bool 类型并不须要作为闭包表达式定义中的一部分。

    由于全部的类型都能够被正确判断,返回箭头 (->) 和 环绕在參数周围的括号也能够被省略:
    reversed = sort(names, { s1, s2 in return s1 > s2 } ) 




    实际上不论什么情况下,通过内联闭包表达式构造的闭包作为參数传递给函数时,都能够判断出闭包的參数和返回值类型,这意味着您差点儿不须要利用完整格式构造不论什么内联闭包。


     然而。你也能够使用明白的类型。如果你想它避免读者阅读可能存在的歧义。这样还是值得鼓舞的。

    这个排序函数样例,闭包的目的是非常明白的,即排序被替换,并且对读者来说能够安全的如果闭包可能会使用字符串值,由于它正协助一个字符串数组进行排序。


    单行表达式闭包能够省略 return单行表达式闭包能够通过隐藏 return keyword来隐式返回单行表达式的结果,如上版本号的样例能够改写为:
    reversed = sort(names, { s1, s2 in s1 > s2 } ) 




    在这个样例中。sort 函数的第二个參数函数类型明白了闭包必须返回一个 Bool 类型值。 由于闭包函数体仅仅包括了一个单一表达式 (s1 > s2),该表达式返回 Bool 类型值。因此这里没有歧义,returnkeyword能够省略。
    參数名简写Swift 自己主动为内联函数提供了參数名称简写功能,您能够直接通过 $0,$1,$2等名字来引用的闭包的參数的值。




     假设您在闭包表达式中使用參数名称简写,您能够在闭包參数列表中省略对其的定义,而且相应參数名称简写的类型会通过函数类型进行判断。 in keyword也相同能够被省略。由于此时闭包表达式全然由闭包函数体构成:
    reversed = sort(names, { $0 > $1 } ) 




    在这个样例中,$0 和 $1 表示闭包中第一个和第二个 String 类型的參数。


    运算符函数实际上另一种更简短的方式来撰写上面样例中的闭包表达式。

    Swift的 String 类型定义了关于大于号 (>) 的字符串实现。让其作为一个函数接受两个 String 类型的參数并返回 Bool 类型的值。

    而这正好与 sort 函数的第二个參数须要的函数类型相符合。 因此,您能够简单地传递一个大于号,Swift能够自己主动判断出您想使用大于号的字符串函数实现:
    reversed = sort(names, >) 




    很多其它关于运算符表达式的内容请查看 Operator Functions 。
    Trailing 闭包假设您须要将一个非常长的闭包表达式作为最后一个參数传递给函数。能够使用 trailing 闭包来增强函数的可读性。


     Trailing 闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其作为最后一个參数调用。


    func someFunctionThatTakesAClosure(closure: () -> ()) {
        // 函数体部分
    }
    // 下面是不使用 trailing 闭包进行函数调用
    someFunctionThatTakesAClosure({
        // 闭包主体部分
    })
    // 下面是使用 trailing 闭包进行函数调用
    someFunctionThatTakesAClosure() {
        // 闭包主体部分





    注意:假设函数仅仅须要闭包表达式一个參数,当您使用 trailing 闭包时,您甚至能够把 () 省略掉。 NOTE
    在上例中作为 sort 函数參数的字符串排序闭包能够改写为:
    reversed = sort(names) { $0 > $1 } 




    当闭包很长以至于不能在一行中进行书写时,Trailing 闭包就变得很实用。 举例来说。Swift 的 Array 类型有一个 map 方法,其获取一个闭包表达式作为其唯一參数。

    数组中的每个元素调用一次该闭包函数,并返回该元素所映射的值(也能够是不同类型的值)。 详细的映射方式和返回值类型由闭包来指定。


     当提供给数组闭包函数后。map 方法将返回一个新的数组。数组中包括了与原数组一一相应的映射后的值。




     下例介绍了怎样在 map 方法中使用 trailing 闭包将 Int 类型数组 [16,58,510] 转换为包括相应 String 类型的数组 ["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] 




    上面的代码创建了整数数字到他们的英文名字之间映射字典。 同一时候定义了一个准备转换为字符串的整型数组。




     您如今能够通过传递一个 trailing 闭包给 numbers 的 map 方法来创建相应的字符串版本号数组。 须要注意的时调用 numbers.map不须要在 map 后面包括不论什么括号,由于仅仅须要传递闭包表达式这一个參数,而且该闭包表达式參数通过 trailing 方式进行撰写:
    let strings = numbers.map {
        (var number) -> String in
        var output = ""
        while number > 0 {
            output = digitNames[number % 10]! + output
            number /= 10
        }
        return output
    }
    // strings 常量被判断为字符串类型数组,即 String[]
    // 其值为 ["OneSix", "FiveEight", "FiveOneZero"] 




    map 在数组中为每个元素调用了闭包表达式。 您不须要指定闭包的输入參数 number 的类型,由于能够通过要映射的数组类型进行判断。




     闭包 number 參数被声明为一个变量參数 (变量的详细描写叙述请參看Constant and Variable Parameters),因此能够在闭包函数体内对其进行改动。 闭包表达式制定了返回值类型为 String,以表明存储映射值的新数组类型为 String。


     闭包表达式在每次被调用的时候创建了一个字符串并返回。 其使用求余运算符 (number % 10) 计算最后一位数字并利用digitNames 字典获取所映射的字符串。
    注意:字典 digitNames 下标后跟着一个叹号 (!),由于字典下标返回一个可选值 (optional value),表明即使该 key不存在也不会查找失败。 在上例中,它保证了 number % 10 能够总是作为一个 digitNames 字典的有效下标 key。 因此叹号能够用于强展开 (force-unwrap) 存储在可选下标项中的 String 类型值。
    从 digitNames 字典中获取的字符串被加入到输出的前部,逆序建立了一个字符串版本号的数字。 (在表达式 number % 10中,假设number为16,则返回6。58返回8,510返回0)。


     number 变量之后除以10。 由于其是整数,在计算过程中未除尽部分被忽略。 因此 16变成了1,58变成了5,510变成了51。


     整个过程反复进行。直到 number /= 10 为0。这时闭包会将字符串输出。而map函数则会将字符串加入到所映射的数组中。


     上例中 trailing 闭包语法在函数后整洁封装了详细的闭包功能。而不再须要将整个闭包包裹在 map 函数的括号内。


    捕获 (Caputure)闭包能够在其定义的上下文中捕获常量或变量。

    即使定义这些常量和变量的原作用域已经不存在,闭包仍然能够在闭包函数体内引用和改动这些值。


     Swift最简单的闭包形式是嵌套函数。也就是定义在其它函数体内的函数。

    嵌套函数能够捕获其外部函数全部的參数以及定义的常量和变量。


     下例为一个叫做 makeIncrementor 的函数,其包括了一个叫做 incrementor 嵌套函数。 嵌套函数 incrementor 从上下文中捕获了两个值,runningTotal 和 amount。

    之后 makeIncrementor 将 incrementor 作为闭包返回。 每次调用 incrementor 时。其会以 amount 作为增量添加 runningTotal 的值。
    func makeIncrementor(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        func incrementor() -> Int {
            runningTotal += amount
            return runningTotal
        }
        return incrementor





    makeIncrementor 返回类型为 () -> Int。

    这意味着其返回的是一个函数。而不是一个简单类型值。 该函数在每次调用时不接受參数仅仅返回一个 Int 类型的值。 关于函数返回其它函数的内容,请查看Function Types as Return Types。




     makeIncrementor 函数定义了一个整型变量 runningTotal (初始为0) 用来存储当前添加总数。 该值通过 incrementor 返回。




     makeIncrementor 有一个 Int 类型的參数,其外部命名为 forIncrement, 内部命名为 amount。表示每次 incrementor 被调用时runningTotal 将要添加的量。


     incrementor 函数用来运行实际的添加操作。 该函数简单地使 runningTotal 添加 amount。并将其返回。


     假设我们单独看这个函数。会发现看上去不同平常:
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal





    incrementor 函数并没有获取不论什么參数,可是在函数体内訪问了 runningTotal 和 amount 变量。这是由于其通过捕获在包括它的函数体内已经存在的 runningTotal 和 amount 变量而实现。


     因为没有改动 amount 变量,incrementor 实际上捕获并存储了该变量的一个副本,而该副本随着 incrementor 一同被存储。


     然而。由于每次调用该函数的时候都会改动 runningTotal 的值,incrementor 捕获了当前 runningTotal 变量的引用。而不是只复制该变量的初始值。捕获一个引用保证了当 makeIncrementor 结束时候并不会消失,也保证了当下一次运行 incrementor 函数时,runningTotal 能够继续添加。
    注意:Swift 会决定捕获引用还是拷贝值。 您不须要标注 amount 或者 runningTotal 来声明在嵌入的 incrementor 函数中的使用方式。 Swift 同一时候也处理 runingTotal 变量的内存管理操作。假设不再被 incrementor 函数使用,则会被清除。


    以下为一个使用 makeIncrementor 的样例:
    let incrementByTen = makeIncrementor(forIncrement: 10) 




    该样例定义了一个叫做 incrementByTen 的常量,该常量指向一个每次调用会加10的 incrementor 函数。 调用这个函数多次能够得到下面结果:
    incrementByTen()
    // 返回的值为10
    incrementByTen()
    // 返回的值为20
    incrementByTen()
    // 返回的值为30 




    假设您创建了还有一个 incrementor。其会有一个属于自己的独立的 runningTotal 变量的引用。 以下的样例中,incrementBySevne 捕获了一个新的 runningTotal 变量,该变量和 incrementByTen 中捕获的变量没有不论什么联系:
    let incrementBySeven = makeIncrementor(forIncrement: 7)
    incrementBySeven()
    // 返回的值为7
    incrementByTen()
    // 返回的值为40 




    注意:假设您闭包分配给一个类实例的属性。而且该闭包通过指向该实例或其成员来捕获了该实例,您将创建一个在闭包和实例间的强引用环。

    Swift 使用捕获列表来打破这样的强引用环。

    很多其它信息,请參考 Strong Reference Cycles for Closures。


    闭包是引用类型上面的样例中。incrementBySeven 和 incrementByTen 是常量,可是这些常量指向的闭包仍然能够添加其捕获的变量值。 这是由于函数和闭包都是引用类型。




     不管您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为相应函数/闭包的引用。 上面的样例中,incrementByTen 指向闭包的引用是一个常量。而并不是闭包内容本身。




     这也意味着假设您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:
    let alsoIncrementByTen = incrementByTen
    alsoIncrementByTen()
    // 返回的值为50

  • 相关阅读:
    Python运算符及逻辑运算
    Python编码、流程控制、格式化输出
    Docker私有仓库Harbor部署与使用
    react
    理事会
    关于elementui form表单过长,看不到未填项
    js
    vue 父子传值
    养生
    html知识点
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/7380420.html
Copyright © 2011-2022 走看看