码迷,mamicode.com
首页 > 编程语言 > 详细

Swift2.1 语法指南——闭包

时间:2015-10-09 16:51:28      阅读:316      评论:0      收藏:0      [点我收藏+]

标签:

原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID94

1、闭包(Closures

闭包是独立的函数代码块,可以在代码中被传递和使用。Swift中的闭包与C语言和Objective-C语言中的block、其他语言中的lambda类似。

闭包可以从上下文中捕获和存储任意变量和常量的引用。这就是所谓的闭合并包裹这些变量和常量。Swift会处理捕获过程中的内存管理。

全局函数和嵌套函数实际上就是闭包的特殊情况。闭包采取如下三种形式之一:

- 全局函数是一种有名字但不捕获任何值的闭包

- 嵌套函数是一种有名字并且能捕获封闭的函数作用域内的值的闭包。

- 闭包表达式是以轻量级语法写成的、没有名字的、并且能够捕获其上下文的值的闭包。

2、闭包表达式

闭包表达式拥有简洁的风格,在一般场景下可以进行语法优化。

Swift提供了一个sort(_:)方法,其根据用于排序的闭包的返回值为数组的值进行排序。

例如,数组初始化如下:

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

sort(_:)方法接收一个排序闭包作为参数,该闭包则接收数组的其中两个元素作为参数并返回一个Bool型的值来决定比较的两个元素哪一个排在前面。

因此,用于排序的闭包的函数类型是:(String, String) -> Bool

则用一般函数的形式写,则是:

1 func backwards(s1: String, _ s2: String) -> Bool {
2     return s1 > s2
3 }
4 var reversed = names.sort(backwards)
5 // reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

如果第一个字符串 (s1) 大于第二个字符串 (s2),backwards函数返回true,表示在新的数组中s1应该出现在s2前。 对于字符串中的字符来说,“大于” 表示 “按照字母顺序较晚出现”。 这意味着字母"B"大于字母"A",字符串"Tom"大于字符串"Tim"。 其将进行字母逆序排序,"Barry"将会排在"Alex"之前。

(1)表达式

用闭包表达式来写这个排序闭包,可以简化成:

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

闭包的类型为(String, String) -> Bool,闭包的函数体部分由in关键字引出。

由于上面的例子中,函数体部分较短,闭包可以写成:

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

(2)从上下文中推断类型

由于排序闭包是用来作为参数传递给sort(_:)方法的,Swift就能根据上下文推断出闭包的参数类型和返回值类型。sort(_:)方法被String数组调用,则闭包的类型一定是(String, String) -> Bool。则闭包中的参数类型和返回值类型可以忽略不写:

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

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

(3)从单表达式隐式返回

单表达式的闭包可以忽略return关键字,隐式返回单行表达式的结果。

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

(4)参数名称缩写

Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数。

如果在闭包表达式中使用参数名称缩写,可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。 in关键字也同样可以被省略:

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

其中,$0和$1分别对应第一个和第二个String参数

(5)运算符函数

Swift 的String类型定义了关于大于号 (>) 的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。 而这正好与sort(_:)方法的第二个参数需要的函数类型相符合。 因此,可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现:

1 reversed = names.sort(>)

3、尾随闭包(Trailing Closures)

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

 1 func someFunctionThatTakesAClosure(closure: () -> Void) {
 2     // function body goes here
 3 }
 4  
 5 // here‘s how you call this function without using a trailing closure:
 6  
 7 someFunctionThatTakesAClosure({
 8     // closure‘s body goes here
 9 })
10  
11 // here‘s how you call this function with a trailing closure instead:
12  
13 someFunctionThatTakesAClosure() {
14     // trailing closure‘s body goes here
15 }

用尾随闭包,可以将上面的排序闭包写成:

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

如果闭包表达式是函数或方法的唯一参数,则可以省略括号:

1 reversed = names.sort { $0 > $1 }

当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。

例如,Swift 的数组有一个map方法,其获取一个闭包表达式作为其唯一参数。 数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(可能是不同类型的值)。 具体的映射方式和返回值类型由闭包来指定。

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

 下面的例子中,将用map(_:)方法将Int数组映射为String数组。

1 let digitNames = [
2     0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
3     5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
4 ]
5 let numbers = [16, 58, 510]

现在,传递一个尾随闭包给numbersmap方法来创建对应的字符串版本数组:

 1 let strings = numbers.map {
 2     (var number) -> String in
 3     var output = ""
 4     while number > 0 {
 5         output = digitNames[number % 10]! + output
 6         number /= 10
 7     }
 8     return output
 9 }
10 // strings is inferred to be of type [String]
11 // its value is ["OneSix", "FiveEight", "FiveOneZero"]

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

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

4、捕获值

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

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

下例为一个函数makeIncrementor,它包含了一个嵌套函数incrementor。 嵌套函数incrementor从上下文中捕获了两个值,runningTotalamount。 之后makeIncrementorincrementor作为闭包返回。 每次调用incrementor时,其会以amount作为增量增加runningTotal的值。

1 func makeIncrementer(forIncrement amount: Int) -> () -> Int {
2     var runningTotal = 0
3     func incrementer() -> Int {
4         runningTotal += amount
5         return runningTotal
6     }
7     return incrementer
8 }

makeIncrementer的返回类型是 () -> Int。

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

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

incrementor函数用来执行实际的增加操作,单独来看这个函数:

1 func incrementer() -> Int {
2     runningTotal += amount
3     return runningTotal
4 }

incrementer函数并没有任何参数,但是在函数体内访问了runningTotalamount变量。这是因为其捕获在包含它的函数体内已经存在的runningTotalamount变量的引用(reference)。捕捉了变量引用,保证了runningTotalamount变量在调用完makeIncrementer后不会消失,并且保证了在下一次执行incrementer函数时,runningTotal可以继续增加。

下面,来使用makeIncrementer:

1 let incrementByTen = makeIncrementer(forIncrement: 10)

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

1 incrementByTen()
2 // returns a value of 10
3 incrementByTen()
4 // returns a value of 20
5 incrementByTen()
6 // returns a value of 30

创建了另一个incrementor,其会有一个属于自己的独立的runningTotal变量的引用:

1 let incrementBySeven = makeIncrementer(forIncrement: 7)
2 incrementBySeven()
3 // returns a value of 7

调用incrementByTen不会影响incrementBySeven中的runningTotal变量:

1 incrementByTen()
2 // returns a value of 40

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

5、闭包是一种引用类型

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

1 let alsoIncrementByTen = incrementByTen
2 alsoIncrementByTen()
3 // returns a value of 50

6、自动闭包

自动闭包是一种自动创建的闭包,其用于绑定一个表达式并将其作为参数传递给函数。自动闭包并不接收任何参数,当它被调用时,它返回绑定在其内部的表达式的值。

通常会调用接收自动闭包的函数,很少实现这种函数。

例如,assert(condition:message:file:line:)函数接收自动闭包作为condition和message参数。其中,只有在Debug情况下才考虑condition参数,而只有当condition等于false的时候,才执行message。

自动闭包可以允许你惰性求值,内部代码直到调用闭包才会执行。惰性求值可以让你控制何时执行,这在计算消耗很大的情况下非常有用。

 1 var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
 2 print(customersInLine.count)
 3 // prints "5"
 4  
 5 let customerProvider = { customersInLine.removeAtIndex(0) }
 6 print(customersInLine.count)
 7 // prints "5"
 8  
 9 print("Now serving \(customerProvider())!")
10 // prints "Now serving Chris!"
11 print(customersInLine.count)
12 // prints "4"

直到闭包真正被调用,customersInLine的第一个元素才被移除。需要注意的是:customerProvider的类型不是String,而是()->String。

传递一个闭包作为函数的参数,同样有惰性求值的效果:

1 // customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
2 func serveCustomer(customerProvider: () -> String) {
3     print("Now serving \(customerProvider())!")
4 }
5 serveCustomer( { customersInLine.removeAtIndex(0) } )
6 // prints "Now serving Alex!"

下面是另一版本的serveCustomer,通过@autoclosure,自动将传入的表达式转换成自动闭包。

1 // customersInLine is ["Ewa", "Barry", "Daniella"]
2 func serveCustomer(@autoclosure customerProvider: () -> String) {
3     print("Now serving \(customerProvider())!")
4 }
5 serveCustomer(customersInLine.removeAtIndex(0))
6 // prints "Now serving Ewa!"

 @autoclosure属性隐含了@noescape属性,这意味着,这个闭包只能用在函数内部。如果想让闭包能够逃离当前的作用域,在函数返回后闭包还能被调用,则使用 @autoclosure(escaping)属性:

 1 // customersInLine is ["Barry", "Daniella"]
 2 var customerProviders: [() -> String] = []
 3 func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
 4     customerProviders.append(customerProvider)
 5 }
 6 collectCustomerProviders(customersInLine.removeAtIndex(0))
 7 collectCustomerProviders(customersInLine.removeAtIndex(0))
 8  
 9 print("Collected \(customerProviders.count) closures.")
10 // prints "Collected 2 closures."
11 for customerProvider in customerProviders {
12     print("Now serving \(customerProvider())!")
13 }
14 // prints "Now serving Barry!"
15 // prints "Now serving Daniella!"

在上面的代码中,没有将闭包用作customer参数,而是将闭包添加到customerProviders数组,这个闭包数组是在函数外部声明的,这意味着,数组中的闭包可以在函数返回后执行。所以,customer参数的值也就可以逃离函数的作用域。

Swift2.1 语法指南——闭包

标签:

原文地址:http://www.cnblogs.com/tt2015-sz/p/4864158.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!