Swift3.0P1 语法指南——闭包
1、闭包(Closures)
闭包是独立的函数代码块,可以在代码中被传递和使用。Swift中的闭包与C语言和Objective-C语言中的block、其他语言中的lambda类似。
闭包可以从上下文中捕获和存储任意变量和常量的引用。这就是所谓的闭合并包裹这些变量和常量。Swift会处理捕获过程中的内存管理。
全局函数和嵌套函数实际上就是闭包的特殊情况。
闭包采取如下三种形式之一:
- 全局函数是一种有名字但不捕获任何值的闭包
- 嵌套函数是一种有名字并且能捕获封闭的函数作用域内的值的闭包。
- 闭包表达式是以轻量级语法写成的、没有名字的、并且能够捕获其上下文的值的闭包。
2、闭包表达式
闭包表达式拥有简洁的风格,在一般场景下可以进行语法优化。
Swift提供了一个sorted(isOrderedBefore:) 方法,其根据用于排序的闭包的返回值为数组的值进行排序。
例如,数组初始化如下:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
sorted(isOrderedBefore:)方法接收一个排序闭包作为参数,该闭包则接收数组的其中两个元素作为参数并返回一个Bool型的值来决定比较的两个元素哪一个排在前面。
因此,用于排序的闭包的函数类型是:(String, String) -> Bool
则用一般函数的形式写,则是:
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sorted(isOrderedBefore: backward)
// reversed is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
如果第一个字符串 (s1
) 大于第二个字符串 (s2
),backward
函数返回true
,表示在新的数组中s1
应该出现在s2
前。
对于字符串中的字符来说,“大于” 表示 “按照字母顺序较晚出现”。 这意味着字母"B"
大于字母"A"
,字符串"Tom"
大于字符串"Tim"
。
这个函数将进行字母逆序排序,"Barry"
将会排在"Alex"
之前。
(1)表达式
用闭包表达式来写这个排序闭包,可以简化成:
reversedNames = names.sorted(isOrderedBefore: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
下面是V2.1的对应方法:
1 reversed = names.sort({ (s1: String, s2: String) -> Bool in
2 return s1 > s2
3 })
闭包的类型为(String, String) -> Bool,闭包的函数体部分由in关键字引出。
由于上面的例子中,函数体部分较短,闭包可以写成:
reversedNames = names.sorted(isOrderedBefore: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
(2)从上下文中推断类型
由于排序闭包是用来作为参数传递给sorted(isOrderedBefore:) 方法的,Swift就能根据上下文推断出闭包的参数类型和返回值类型。
sorted(isOrderedBefore:)方法被String数组调用,则闭包的类型一定是(String, String) -> Bool。则闭包中的参数类型和返回值类型可以忽略不写:
reversedNames = names.sorted(isOrderedBefore: { s1, s2 in return s1 > s2 } )
实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。
(3)从单表达式隐式返回
单表达式的闭包可以忽略return关键字,隐式返回单行表达式的结果。
reversedNames = names.sorted(isOrderedBefore: { s1, s2 in s1 > s2 } )
(4)参数名称缩写
Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过$0
,$1
,$2
来顺序调用闭包的参数。
如果在闭包表达式中使用参数名称缩写,可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。 in
关键字也同样可以被省略:
reversed = names.sorted(isOrderedBefore: { $ > $ } )
其中,$0和
$1分别对应第一个和第二个String参数
(5)运算符函数
Swift 的String
类型定义了关于大于号 (>
) 的字符串实现,其作为一个函数接受两个String
类型的参数并返回Bool
类型的值。 而这正好与sorted(isOrderedBefore:)需要的
方法的第二个参数需要的函数类型相符合。 因此,可以简单地传递一个>,Swift可以自动推断出您想使用>的字符串函数实现:
reversed = names.sorted(isOrderedBefore: >)
3、尾随闭包(Trailing Closures)
如果需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。 尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
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
}
用尾随闭包,可以将上面的排序闭包写成:
reversed = names.sorted() { $ > $ }
如果闭包表达式是函数或方法的唯一参数,则可以省略括号:
reversed = names.sorted { $ > $ }
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。
例如,Swift 的数组
有一个map
方法,其获取一个闭包表达式作为其唯一参数。 对于数组中的每一个元素,调用一次这个闭包函数,就会返回与该元素所映射的值(可能是不同类型的值)。 具体的映射方式和返回值类型由闭包来指定。
当给数组的每个元素提供闭包函数后,map
方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。
下面的例子中,将用map(_:)方法将Int数组映射为String数组。
let digitNames = [
: "Zero", : "One", : "Two", : "Three", : "Four",
: "Five", : "Six", : "Seven", : "Eight", : "Nine"
]
let numbers = [, , ]
现在,传递一个尾随闭包给numbers
的map
方法来创建对应的字符串版本数组:
let strings = numbers.map {
(number) -> String in
var number = number
var output = ""
while number > {
output = digitNames[number % ]! + output
number /=
}
return output
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
V2.1:
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
字典获取所映射的字符串。
注意:在字典中用下标取值时取出的是optional类型。由于这里可以保证number % 10是一个有效的下标,所以用了强制解绑(!)。
4、捕获值
闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
在Swift中,最简单的捕获值的闭包形式就是嵌套函数。 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
下例为一个函数makeIncrementor,它包含了一个嵌套函数incrementor。 嵌套函数incrementor
从上下文中捕获了两个值,runningTotal
和amount
。 之后makeIncrementor
将incrementor
作为闭包返回。 每次调用incrementor
时,其会以amount
作为增量增加runningTotal
的值。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal =
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
makeIncrementer的返回类型是 () -> Int。
makeIncrementer函数定义了一个整型变量runningTotal
(初始为0) 用来存储当前跑步总数。 该值通过incrementor
返回。
makeIncrementor
有一个Int
类型的参数,其外部命名为forIncrement
, 内部命名为amount
,表示每次incrementor
被调用时runningTotal
将要增加的量。
incrementor
函数用来执行实际的增加操作,单独来看这个函数:
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
incrementer
函数并没有任何参数,但是在函数体内访问了runningTotal
和amount
变量。这是因为其捕获在包含它的函数体内已经存在的runningTotal
和amount
变量的引用(reference)。捕捉了变量引用,保证了runningTotal
和amount
变量在调用完makeIncrementer
后不会消失,并且保证了在下一次执行incrementer
函数时,runningTotal
可以继续增加。
下面,来使用makeIncrementer:
let incrementByTen = makeIncrementer(forIncrement: )
定义了一个叫做incrementByTen
的常量,该常量指向一个每次调用会加10的incrementor
函数。 调用这个函数多次可以得到以下结果:
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30
创建了另一个incrementor
,其会有一个属于自己的独立的runningTotal
变量的引用:
let incrementBySeven = makeIncrementer(forIncrement: )
incrementBySeven()
// returns a value of 7
调用incrementByTen不会影响incrementBySeven中的runningTotal
变量:
incrementByTen()
// returns a value of 40
注意:如果把闭包赋值给一个类实例的属性,并且该闭包通过指向该实例或其成员来捕获该实例,将创建一个在闭包和实例间的强引用环。 Swift 使用捕获列表来打破这种强引用环。
5、闭包是一种引用类型
如果将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50
6、非逃逸闭包(Nonescaping Closures)
一个闭包被当做一个参数传递给一个函数,但这个闭包在函数返回后仍然被调用,这就叫做这个闭包逃离了这个函数。
在定义一个需要把闭包作为参数的函数时,可以在参数类型前加上@noescape标记,提示这个闭包不允许逃离函数(不能在函数返回后被调用)。
让闭包带着@noescape标记,可以让编译器在了解闭包的生命周期的情况下做更多的aggressive optimizations。
func someFunctionWithNonescapingClosure(closure: @noescape () -> Void) {
closure()
}
一种闭包可以逃离函数的方式是,在函数体外将闭包定义为一个变量。例如,很多开始异步操作的函数都接收一个闭包参数作为结束句柄(completion handler),函数在开始这个异步操作后就返回了,但是这个闭包是在异步操作结束之后才被调用,这种情况下,闭包就需要逃离函数(滞后调用)。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_:)函数接收一个闭包参数,并把这个闭包加到了定义在函数体外的闭包数组中。如果你在这里用了@noescape标记,就会产生编译错误。
将闭包标记为@noescape,可以在闭包中直接引用到self:
class SomeClass {
var x =
func doSomething() {
someFunctionWithNonescapingClosure { x = }
someFunctionWithEscapingClosure { self.x = }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100"
7、自动闭包(Autoclosures)
自动闭包是一种自动创建的闭包,其用于绑定一个表达式并将其作为参数传递给函数。自动闭包并不接收任何参数,当它被调用时,它返回绑定在其内部的表达式的值。
通常会调用接收自动闭包的函数,很少去实现这种函数。
例如,assert(condition:message:file:line:)函数接收自动闭包作为condition和message参数。其中,只有在Debug情况下才考虑condition参数,而只有当condition等于false的时候,才执行message。
自动闭包可以允许你惰性求值,内部代码直到调用闭包才会执行。惰性求值可以让你控制何时执行,这在计算消耗很大的情况下非常有用。
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// prints "5" let customerProvider = { customersInLine.removeAtIndex() }
print(customersInLine.count)
// prints "5" print("Now serving \(customerProvider())!")
// prints "Now serving Chris!"
print(customersInLine.count)
// prints "4"
直到闭包真正被调用,customersInLine的第一个元素才被移除。需要注意的是:customerProvider的类型不是String,而是()->String。
传递一个闭包作为函数的参数,同样有惰性求值的效果:
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: ) } )
// Prints "Now serving Alex!"
下面是另一版本的serve函数,通过@autoclosure,自动将传入的表达式转换成自动闭包。
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: ))
// Prints "Now serving Ewa!"
@autoclosure属性隐含了@noescape属性,这意味着,这个闭包只能用在函数内部。如果想让闭包能够逃离当前的作用域,在函数返回后闭包还能被调用,则使用
@autoclosure(escaping)属性:
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure(escaping) () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: ))
collectCustomerProviders(customersInLine.remove(at: ))
print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
在上面的代码中,没有将闭包用作customer参数进行调用,而是将闭包添加到customerProviders数组,这个闭包数组是在函数外部声明的,这意味着,数组中的闭包可以在函数返回后执行。所以,customer参数的值也就可以逃离函数的作用域。
Swift3.0P1 语法指南——闭包的更多相关文章
- Swift3.0P1 语法指南——构造器
原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...
- Swift3.0P1 语法指南——属性
原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...
- Swift3.0P1 语法指南——下标
原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...
- Swift3.0P1 语法指南——方法
原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...
- Swift3.0P1 语法指南——类和结构体
原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...
- Swift3.0P1 语法指南——枚举
原档: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programm ...
- Swift3.0P1 语法指南——函数
原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...
- Swift3.0P1 语法指南——基本操作符
原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...
- Swift3.0P1 语法指南——继承
原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...
随机推荐
- Oracle 数据库基础——安装
一.数据库基础知识 1.概念 数据库全称数据库管理系统,简称DBMS,是一种在计算机中,针对数据进行管理.存储.共享的一种技术. 2.分类 数据库的发展过程中,按逻辑模型可分为以下几种: 3.关系型数 ...
- 第三章SQL编程
本章目标: 1.使用变量 2.输出语句 3.数据类型转换 4.逻辑控制语句 5.批处理 一.变量 1.什么是变量呢? 变量是存储数据的容器 T-SQL中的变量分为局部变量和全局变量 2.局部变量 局部 ...
- ES5/标准 ECMAScript 内置对象
https://www.w3.org/html/ig/zh/wiki/ES5/%E6%A0%87%E5%87%86_ECMAScript_%E5%86%85%E7%BD%AE%E5%AF%B9%E8% ...
- Java8并发教程:Threads和Executors
来之:ImportNew 欢迎阅读我的Java8并发教程的第一部分.这份指南将会以简单易懂的代码示例来教给你如何在Java8中进行并发编程.这是一系列教程中的第一部分.在接下来的15分钟,你将会学会如 ...
- 熟悉MyEclipse
资源网址:http://www.myeclipsecn.com/learningcenter/ 20151126 [从这里开始]量身打造自己的MyEclipse(多图) 主要讲在MyEclipse里面 ...
- vector容器删除某些元素且释放内存
1,size和capacity size: 指目前容器中实际有多少元素,对应的resize(size_type)会在容器尾添加或删除一些元素,来调整容器中实际的内容,使容器达到指定的大小. capac ...
- 40个让你的网站屌到爆的jQuery插件
一 个插件的基本功能是执行一个含有元素集合的函数数组.每个方法和jQuery核心组成一个插件,如.fadeOut()或.addClass().一个 jQuery插件是一个基本的可以扩充jQuery 原 ...
- 【swift学习笔记】三.使用xib自定义UITableViewCell
使用xib自定义tableviewCell看一下效果图 1.自定义列 新建一个xib文件 carTblCell,拖放一个UITableViewCell,再拖放一个图片和一个文本框到tableviewc ...
- 集合覆盖 顶点覆盖: set cover和vertex cover
这里将讲解一下npc问题中set cover和vertex cover分别是什么. set cover: 问题定义: 实例:现在有一个集合A,其中包含了m个元素(注意,集合是无序的,并且包含的元素也是 ...
- github-提交仓库
git提交仓库主要分3快 1.用命令git add告诉Git,把文件添加到本地仓库(可以用.代替提交所有) $ git add readme.txt 2.用命令git commit告诉Git,把文件提 ...