加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Swift闭包详解

发布时间:2020-12-14 02:35:39 所属栏目:百科 来源:网络整理
导读:参考网址:http://c.biancheng.net/cpp/html/2285.html 大家注意哦,由于swift的更新,参考网址里的代码已经不能跑起来了,我结合自己的理解整理如下。 在Swift函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一: 全局函数是

参考网址:http://c.biancheng.net/cpp/html/2285.html
大家注意哦,由于swift的更新,参考网址里的代码已经不能跑起来了,我结合自己的理解整理如下。
在Swift函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
全局函数是一个有名字但不会捕获任何值的闭包
嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的没有名字的闭包

Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中以实现语法优化,主要优化如下:
利用上下文推断参数和返回值类型
单表达式(single-expression)闭包可以省略 return 关键字
参数名称简写
Trailing 闭包语法
闭包表达式

嵌套函数是一种在较复杂函数中方便进行命名和定义自包含代码模块的方式。 当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在处理一些函数并需要将另外一些函数作为该函数的参数时。

闭包表达式是一种利用简洁语法构建内联闭包的方式。 闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。 下面闭包表达式的例子通过使用几次迭代展示了 sort 函数定义和语法优化的方式。 每一次迭代都用更简洁的方式描述了相同的功能。

sort 函数

Swift 标准库提供了 sort 函数,会根据您提供的排序闭包将已知类型数组中的值进行排序。 一旦排序完成,函数会返回一个与原数组大小相同的新数组,该数组中包含已经正确排序的同类型元素。
示例代码如下:

let names = ["Chris","Alex","Ewa","Barry","Daniella"] ;

        func backwards(s1: String,s2: String) -> Bool {
            return s1 > s2
        }

        let reversed = names.sort( backwards)
        print(reversed)
// reversed is equal to ["Ewa","Daniella","Chris","Alex"]

闭包表达式语法

闭包表达式语法有如下一般形式:

{ (parameters) -> returnType in
    statements
}

上面的例子可以写成这个样子:

let reversed = names.sort( { (s1: String,s2: String) -> Bool in return s1 > s2 }) 

根据上下文推断类型

因为排序闭包是作为函数的参数进行传入的,Swift可以推断其参数和返回值的类型。 sort 期望参数是类型为 (String,String) -> Bool 的函数,因此实际上 String,String 和 Bool 类型并不需要作为闭包表达式定义中的一部分。 因为所有的类型都可以被正确推断,返回箭头 (->) 和 围绕在参数周围的括号也可以被省略:

let reversed = names.sort( { s1,s2 in return s1 > s2 })

实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。

然而,你也可以使用明确的类型,如果你想它避免读者阅读可能存在的歧义,这样还是值得鼓励的。这个排序函数例子,闭包的目的是很明确的,即排序被替换,而且对读者来说可以安全的假设闭包可能会使用字符串值,因为它正协助一个字符串数组进行排序。

单行表达式闭包可以省略 return

单行表达式闭包可以通过隐藏 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:

let reversed = names.sort( { s1,s2 in s1 > s2 })

参数名简写

Swift 自动为内联函数提供了参数名称简写功能,您可以直接通过 $0,$1,$2等名字来引用的闭包的参数的值。

let reversed = names.sort({ $0 > $1})

运算符函数

实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。 Swift的 String 类型定义了关于大于号 (>) 的字符串实现,让其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。 而这正好与 sort 函数的参数需要的函数类型相符合。 因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现:

let reversed = names.sort(>)

Trailing 闭包

如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用 trailing 闭包来增强函数的可读性。
Trailing 闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其作为最后一个参数调用

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

上面的例子可以改为:

let reversed = names.sort(){ $0 > $1 }

注意:如果函数只需要闭包表达式一个参数,当您使用 trailing 闭包时,您甚至可以把 () 省略掉。 NOTE
那我们试试如下:

let reversed = names.sort{ $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,520]

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

您现在可以通过传递一个 trailing 闭包给 numbers 的 map 方法来创建对应的字符串版本数组。 需要注意的时调用 numbers.map不需要在 map 后面包含任何括号,因为只需要传递闭包表达式这一个参数,并且该闭包表达式参数通过 trailing 方式进行撰写:

 let numbers = [16,520] let strings = numbers.map { (var number) -> String in var output = "" while number > 0 { output = digitNames[number%10]! + output number /= 10 } return output } print(strings); //["OneSix","FiveEight","FiveTwoZero"]

map 在数组中为每一个元素调用了闭包表达式。 您不需要指定闭包的输入参数 number 的类型,因为可以通过要映射的数组类型进行推断。

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

闭包表达式在每次被调用的时候创建了一个字符串并返回。 其使用求余运算符 (number % 10) 计算最后一位数字并利用digitNames 字典获取所映射的字符串。

注意:字典 digitNames 下标后跟着一个叹号 (!),因为字典下标返回一个可选值 (optional value),表明即使该 key不存在也不会查找失败。 在上例中,它保证了 number % 10 可以总是作为一个 digitNames 字典的有效下标 key。 因此叹号可以用于强展开 (force-unwrap) 存储在可选下标项中的 String 类型值。

捕获 (Capture)
闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数体内的函数。 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。

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 函数使用,则会被清除。

该例子定义了一个叫做 incrementByTen 的常量,该常量指向一个每次调用会加10的 incrementor 函数。 调用这个函数多次可以得到以下结果:

func makeIncrementor(forIncrement amount: Int) ->() ->Int {

            var runningTotal = 0

            func incrementor() ->Int {

                runningTotal += amount

                return runningTotal
            }

            return incrementor
        }



let incrementByTen = makeIncrementor(forIncrement: 10)
        print(incrementByTen()) //10
        print(incrementByTen())  //20
        print(incrementByTen())  //30

如果您创建了另一个 incrementor,其会有一个属于自己的独立的 runningTotal 变量的引用。 下面的例子中,incrementBySevne 捕获了一个新的 runningTotal 变量,该变量和 incrementByTen 中捕获的变量没有任何联系:

let incrementBySeven = makeIncrementor(forIncrement: 7)
        print(incrementBySeven()) //7
        print(incrementByTen()) //40

注意:如果您闭包分配给一个类实例的属性,并且该闭包通过指向该实例或其成员来捕获了该实例,您将创建一个在闭包和实例间的强引用环。 Swift 使用捕获列表来打破这种强引用环。更多信息,请参考 Strong Reference Cycles for Closures。

闭包是引用类型

上面的例子中,incrementBySeven 和 incrementByTen 是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。 这是因为函数和闭包都是引用类型。

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

这也意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回的值为50

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读