Swift基础学习

Swift3.0闭包(Closures)

2016-08-31  本文已影响618人  Mustard_Buli

闭包是功能性自包含模块,可以在代码中被传递和使用。 Swift 中的闭包与 C 和 Objective-C中的 blocks 以及其他一些编程语言中的 lambdas(λ) 比较相似。

闭包可以捕获和存储其所在上下文中任意常量和变量的引用。 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift会为您管理在捕获过程中涉及到的内存操作。

函数 中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:

Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中以实现语法优化,主要优化如下:

闭包表达式(Closure Expressions)

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

排序方法(The Sorted Method)

Swift标准库中提供了一个叫做 sorted(by:) 的方法,对会根据提供的值对已知类型的数组进行排序。方法会返回一个已经排序好的数组,但原始的数组不会改变。


The Sorted Method🌰

闭包表达式语法(Closure Expression Syntax)

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

{ (parameters) -> return type in
    statements
}

在闭包语法表达式中的参数可以作为 in-out 参数,但是不能有默认值。亦可以使用可变参数。元组也可以作为参数和返回值。
上面的🌰也可以这样写:


Closure Expression Syntax🌰

根据上下文推断类型(Interring Type From Context)

因为Swift中有根据上下文推断类型,所以上面的例子可以简写成这样:


Interring Type From Context🌰

单表达式闭包省略 return 关键(Implicit Returns from Single-Expression Closures)

因为Swift的这条特性,所以上面的🌰又可以简化成:


Implicit Returns from Single-Expression Closures🌰

参数名称简写(Shorthand Argument Names)

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


Shorthand Argument Names🌰

感觉有点der啊。

运算符函数(Operator Functions)

实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。 Swift的 String 类型定义了关于大于号 (>) 的字符串实现,让其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。 而这正好与 sort 函数的第二个参数需要的函数类型相符合。就是说可以这么写:


Operator Functions🌰

后置闭包(Trailing Closures)

如果需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用后置闭包代替。后置闭包在函数结束的括号后面写,尽管如此,它仍是函数的参数。当使用后置闭包语法的时候,不用写参数标签作为函数调用的一部分:


Trailing Closures🌰

所以上面的那个🌰就可以这么写:


还是那个🌰

当后置闭包只需要一个参数的时候,『( )』可以省略:


一直是那个🌰

当闭包非常长以至于不能在一行中进行书写时,后置闭包就变得非常有用。 举例来说,Swift 的 Array 类型有一个 map 方法,其获取一个闭包表达式作为其唯一参数。 数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。 具体的映射方式和返回值类型由闭包来指定:

换了个🌰
  • 这个🌰有点复杂,就是当调用map函数的时候,先将16拿出来进行映射。这个后置闭包的参数就是数组中每个元素,返回值是『翻译』之后的字符串。

捕获值(Capturing Values)

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

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


🌰

如果我们单独来看这个函数:

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

一目了然,这个函数并没有传进来任何参数,但是在函数体内访问了 runningTotal 和 amount 变量,这就是因为这个闭包捕获了上下文的值。

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

然而,因为每次调用该函数的时候都会修改 runningTotal 的值,incrementor 捕获了当前 runningTotal 变量的引用,而不是仅仅复制该变量的初始值。捕获一个引用保证了当 makeIncrementor 结束时候并不会消失,也保证了当下一次执行 incrementor 函数时,runningTotal 可以继续增加。

注意:『As an optimization(优化), Swift may instead capture and store a copy of a value if that value is not mutated(突变) by a closure, and if the value is not mutated after the closure is created.』(这句话不知道怎么翻译了。😂)
Swift 同时也处理 runingTotal 变量的内存管理操作,如果不再被 incrementor 函数使用,则会被清除。

makeIncrementr的使用🌰

如果闭包分配给一个类实例的属性,并且该闭包通过指向该实例或其成员来捕获了该实例,将创建一个在闭包和实例间的强引用环。 Swift 使用捕获列表来打破这种强引用环。

闭包是引用类型(Closures Are Reference Types)

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

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

这也就是说如果将闭包赋值给了两个不同的常量(变量),两个值都会指向同一个闭包:


证明🌰

非逃逸闭包(Nonescaping Closures)

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸

当你定义接受闭包作为参数的函数时,你可以在类型之前标注 『@noescape』,用来指明这个闭包是不允许“逃逸”出这个函数的。将闭包标注 @noescape 能使编译器知道这个闭包的生命周期,从而可以进行一些比较激进的优化。

但是这里需要提一下的就是:闭包只能在函数体中执行,所以编译器明确的知道运行时的上下文:


🌰

这里会报错竟然是因为@noescape 是默认的。在文档中,这段的例子好像默认都是@escape,但是在新的版本中默认是@noescape,不知道是我的理解错误了还是文档的问题。不过问题已经反馈了。

自动闭包(Autoclosures)

这章剩下的部分,等我先看别的再回过头来看。。一脸懵逼ing。

上一篇下一篇

猜你喜欢

热点阅读