Swift 闭包/枚举
1. Swift 闭包
闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。
Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。
全局函数和嵌套函数其实就是特殊的闭包。
闭包的形式有:
全局函数 | 嵌套函数 | 闭包表达式 |
---|---|---|
有名字但不能捕获任何值。 | 有名字,也能捕获封闭函数内的值。 | 无名闭包,使用轻量级语法,可以根据上下文环境捕获值。 |
Swift中的闭包有很多优化的地方:
- 根据上下文推断参数和返回值类型
- 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略return)
- 可以使用简化参数名,如$0, $1(从0开始,表示第i个参数…)
- 提供了尾随闭包语法(Trailing closure syntax)
语法:
以下定义了一个接收参数并返回指定类型的闭包语法:
{(parameters) -> return type in
statements
}
实例:
import Cocoa
let studname = { print("Swift 闭包实例。") }
studname()
以上程序执行输出结果为:
Swift 闭包实例。
以下闭包形式接收两个参数并返回布尔值:
{(Int, Int) -> Bool in
Statement1
Statement 2
---
Statement n
}
实例:
import Cocoa
let divide = {(val1: Int, val2: Int) -> Int in
return val1 / val2
}
let result = divide(200, 20)
print (result)
以上程序执行输出结果为:
10
1.1 闭包表达式
闭包表达式是一种利用简洁语法构建内联闭包的方式。 闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。
sorted
方法
Swift 标准库提供了名为 sorted(by:)
的方法,会根据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。
排序完成后,sorted(by:)
方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被 sorted(by:)
方法修改。
sorted(by:)
方法需要传入两个参数:
- 已知类型的数组
- 闭包函数,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回
true
,反之返回false
。
实例:
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
// 使用普通函数(或内嵌函数)提供排序功能,闭包函数类型需为(String, String) -> Bool。
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sorted(by: backwards)
print(reversed)
以上程序执行输出结果为:
["S", "D", "BE", "AT", "AE"]
如果第一个字符串 s1
大于第二个字符串 (s2
),backwards
函数返回 true
,表示在新的数组中 s1
应该出现在 s2
前。 对于字符串中的字符来说,“大于” 表示 “按照字母顺序较晚出现”。 这意味着字母 “B” 大于字母 “A”,字符串 “S” 大于字符串 “D”。 其将进行字母逆序排序,“AT” 将会排在 “AE” 之前。
1.2 参数名称缩写
Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过 $0
,$1
,$2
来顺序调用闭包的参数。
实例:
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
var reversed = names.sorted( by: { $0 > $1 } )
print(reversed)
$0
和 $1
表示闭包中第一个和第二个 String
类型的参数。
以上程序执行输出结果为:
["S", "D", "BE", "AT", "AE"]
如果你在闭包表达式中使用参数名称缩写, 您可以在闭包参数列表中省略对其定义, 并且对应参数名称缩写的类型会通过函数类型进行推断。in
关键字同样也可以被省略。
1.3 运算符函数
实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。
Swift 的 String
类型定义了关于大于号 (>) 的字符串实现,其作为一个函数接受两个 String
类型的参数并返回 Bool
类型的值。 而这正好与 sort(_:)
方法的第二个参数需要的函数类型相符合。 因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现:
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
var reversed = names.sorted(by: >)
print(reversed)
以上程序执行输出结果为:
["S", "D", "BE", "AT", "AE"]
1.4 尾随闭包
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure({
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
实例:
import Cocoa
let names = ["AT", "AE", "D", "S", "BE"]
//尾随闭包
var reversed = names.sorted() { $0 > $1 }
print(reversed)
sort()
后的 { $0 > $1}
为尾随闭包。
以上程序执行输出结果为:
["S", "D", "BE", "AT", "AE"]
注意: 如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉。
reversed = names.sorted { $0 > $1 }
1.5 捕获值
闭包可以在其定义的上下文中捕获常量或变量。
即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。
嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
看这个例子:
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
一个函数 makeIncrementor
,它有一个 Int
型的参数 amout
, 并且它有一个外部参数名字 forIncremet
,意味着你调用的时候,必须使用这个外部名字。返回值是一个 ()-> Int
的函数。
函数体内,声明了变量 runningTotal
和一个函数 incrementor
。
incrementor
函数并没有获取任何参数,但是在函数体内访问了 runningTotal
和 amount
变量。这是因为其通过捕获在包含它的函数体内已经存在的 runningTotal
和 amount
变量而实现。
由于没有修改 amount
变量,incrementor
实际上捕获并存储了该变量的一个副本,而该副本随着 incrementor
一同被存储。
所以我们调用这个函数时会累加:
import Cocoa
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
// 返回的值为10
print(incrementByTen())
// 返回的值为20
print(incrementByTen())
// 返回的值为30
print(incrementByTen())
以上程序执行输出结果为:
10
20
30
1.6 闭包是引用类型
上面的例子中,incrementByTen
是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。
这是因为函数和闭包都是引用类型。
无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。 上面的例子中,incrementByTen
指向闭包的引用是一个常量,而并非闭包内容本身。
这也意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:
import Cocoa
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
// 返回的值为10
incrementByTen()
// 返回的值为20
incrementByTen()
// 返回的值为30
incrementByTen()
// 返回的值为40
incrementByTen()
let alsoIncrementByTen = incrementByTen
// 返回的值也为50
print(alsoIncrementByTen())
以上程序执行输出结果为:
50
学习地址:菜鸟教程(https://www.runoob.com/swift/swift-closures.html)
2. Swift 枚举
枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。
Swift 的枚举类似于 Objective C 和 C 的结构,枚举的功能为:
- 它声明在类中,可以通过实例化类来访问它的值。
- 枚举也可以定义构造函数(initializers)来提供一个初始成员值;可以在原始的实现基础上扩展它们的功能。
- 可以遵守协议(protocols)来提供标准的功能。
语法:
Swift 中使用 enum
关键词来创建枚举并且把它们的整个定义放在一对大括号内:
enum enumname {
// 枚举定义放在这里
}
例如我们定义以下表示星期的枚举:
import Cocoa
// 定义枚举
enum DaysofaWeek {
case Sunday
case Monday
case TUESDAY
case WEDNESDAY
case THURSDAY
case FRIDAY
case Saturday
}
var weekDay = DaysofaWeek.THURSDAY
weekDay = .THURSDAY
switch weekDay
{
case .Sunday:
print("星期天")
case .Monday:
print("星期一")
case .TUESDAY:
print("星期二")
case .WEDNESDAY:
print("星期三")
case .THURSDAY:
print("星期四")
case .FRIDAY:
print("星期五")
case .Saturday:
print("星期六")
}
以上程序执行输出结果为:
星期四
枚举中定义的值(如 Sunday,Monday,……和Saturday)是这个枚举的成员值(或成员)。case
关键词表示一行新的成员值将被定义。
注意: 和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的
DaysofaWeek
例子中,Sunday,Monday,……和Saturday不会隐式地赋值为0,1,……和6。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的DaysofaWeek
类型。
var weekDay = DaysofaWeek.THURSDAY
weekDay
的类型可以在它被 DaysofaWeek
的一个可能值初始化时推断出来。一旦 weekDay
被声明为一个 DaysofaWeek
,你可以使用一个缩写语法 .
将其设置为另一个 DaysofaWeek
的值:
var weekDay = .THURSDAY
当weekDay的类型已知时,再次为其赋值可以省略枚举名。使用显式类型的枚举值可以让代码具有更好的可读性。
枚举可分为相关值与原始值。
2.1 相关值与原始值的区别
相关值 | 原始值 |
---|---|
不同数据类型 | 相同数据类型 |
实例: enum {10,0.8,"Hello"} | 实例: enum {10,35,50} |
值的创建基于常量或变量 | 预先填充的值 |
相关值是当你在创建一个基于枚举成员的新常量或变量时才会被设置,并且每次当你这么做得时候,它的值可以是不同的。 | 原始值始终是相同的 |
2.2 相关值
以下实例中我们定义一个名为 Student 的枚举类型,它可以是 Name 的一个字符串(String),或者是 Mark 的一个相关值(Int,Int,Int)。
import Cocoa
enum Student{
case Name(String)
case Mark(Int,Int,Int)
}
var studDetails = Student.Name("Runoob")
var studMarks = Student.Mark(98,97,95)
switch studMarks {
case .Name(let studName):
print("学生的名字是: \(studName)。")
case .Mark(let Mark1, let Mark2, let Mark3):
print("学生的成绩是: \(Mark1),\(Mark2),\(Mark3)。")
}
以上程序执行输出结果为:
学生的成绩是: 98,97,95。
2.3 原始值
原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。
在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值。
例如,当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。
import Cocoa
enum Month: Int {
case January = 1, February, March, April, May, June, July, August, September, October, November, December
}
let yearMonth = Month.May.rawValue
print("数字月份为: \(yearMonth)。")
以上程序执行输出结果为:
数字月份为: 5。
学习地址:菜鸟教程(https://www.runoob.com/swift/swift-enumerations.html)