模式匹配是 switch 的主要功能,模式匹配是指对相应 case 匹配到的值进行解构的能力。解构是指将特定结构的内容再次分解为更小的条目,先看一个例子:
let harry = ("Harry", "Potter", 21, "Wizard")
let (_, surname, _, _) = harry
print(surname)
// Potter
模式类型
Swift 中提供了 8 种模式,分别是:
- 通配符模式
- 标识符模式
- 值绑定模式
- 元组模式
- 枚举用例模式
- 可选模式
- 类型转换模式
- 表达式模式
这些模式不仅能用那个在 switch 语句中,还可以用在 if,guard 和 for 语句中。
通配符模式
通配符模式是指忽略匹配到的值,通过 _ 来实现,看下面的例子:
switch (15, "example", 3.14) {
case (_, _, let pi): print ("pi: \(pi)")
}
// pi: 3.14
标识符模式
匹配一个具体值,这和 Objective-C 的 switch 实现是一样的:
let language = "Japanese"
switch language {
case "Japanese": print("おはようございます")
case "English": print("Hello!")
case "German": print("Guten Tag")
default: print("Other")
}
// おはようございます
值绑定模式
值绑定模式是把匹配到的值绑定给一个变量(let)或常量(var):
let point = (3, 2)
switch point {
// 将 point 中的元素绑定到 x 和 y
case let (x, y):
print("The point is at (\(x), \(y)).")
}
// “The point is at (3, 2).”
元组模式
元组模式是用括号括起来,以逗号分隔的零个或多个模式列表。
let age = 23
let job: String? = "Operator"
let payload: Any = NSDictionary()
switch (age, job, payload) {
case (let age, _, _ as NSDictionary):
print(age)
default: ()
}
// 23
枚举用例模式
枚举用例模式匹配现有的某个枚举类型的某个成员值。枚举用例模式出现在 switch 语句中的 case 标签中,以及 if、while、guard 和 for-in 语句的 case 条件中。
let e = Entities.soldier(x: 4, y: 5)
switch e {
case let .soldier(x, y):
print("x:\(x), y:\(y)")
case let .tank(x, y):
print("x:\(x), y:\(y)")
case let .player(x, y):
print("x:\(x), y:\(y)")
}
// x:4, y:5
可选模式
可选模式由一个标识符后紧随一个 ? 组成,可以像枚举用例模式一样使用它。
let someOptional: Int? = 42
// 使用可选模式匹配
if case let x? = someOptional {
print(x)
}
// 42
case let x? 中的 ? 号表示,如果可选类型有值则匹配,否则不匹配。
类型转换模式
类型转换模式转换或匹配类型,它有 2 种类型:
- is 模式:仅当一个值的类型在运行时和 is 右边的指定类型一致,或者是其子类的情况下,才会匹配。它只做匹配,但不关注返回值。
- as 模式:和 is 模式的匹配规则一致,如果成功的话会将类型转换到左侧指定的模式中。
let a: Any = 5
switch a {
// this fails because a is still Any
// error: binary operator '+' cannot be applied to operands of type 'Any' and 'Int'
case is Int: print (a + 1)
// This works and returns '6'
case let n as Int: print (n + 1)
default: ()
}
表达式模式
表达式模式只出现在 switch 语句中的 case 标签中。它的功能非常强大,它可以把 switch 值和实现 ~= 操作符的表达式进行匹配。
- 范围匹配:
switch 5 {
case 0...10: print("In range 0-10")
default: print("default")
}
// In range 0-10
- 实现 ~= 操作符,匹配所有血量为 0 的实体:
struct Soldier {
let hp: Int
let x: Int
let y: Int
static func ~= (pattern: Int, value: Soldier) -> Bool {
return pattern == value.hp
}
}
let soldier = Soldier(hp: 0, x: 10, y: 10)
switch soldier {
case 0: print("dead soldier")
default: ()
}
// dead soldier
模式匹配在其它语句中的使用
if case let
case let x = y 模式用来检查 y 是否可以和模式 x 匹配。而 if case let x = y { … } 严格等同于 switch y { case let x: … },当只想与一条 case 匹配时,这种更紧凑的语法更有用。有多个 case 时更适合使用 switch。
enum Media {
case book(title: String, author: String, year: Int)
case movie(title: String, director: String, year: Int)
case website(urlString: String)
}
let m = Media.movie(title: "Captain America: Civil War", director: "Russo Brothers", year: 2016)
if case let Media.movie(title, _, _) = m {
print("This is a movie named \(title)")
}
// This is a movie named Captain America: Civil War
// 还可以改为 switch 后更冗长的代码
switch m {
case let Media.movie(title, _, _):
print("This is a movie named \(title)")
default: () // do nothing, but this is mandatory as all switch in Swift must be exhaustive
}
if case let where
我们也可以将 if case let 和 where 语句一起使用,创建多个从属条件,现在的 Swift 版本中,用逗号代替 where,例子如下:
let m = Media.movie(title: "Captain America: Civil War", director: "Russo Brothers", year: 2016)
if case let Media.movie(_, _, year) = m, year < 1888 {
print("Something seems wrong: the movie's year is before the first movie ever made.")
}
guard case let
guard case let 和 if case let 相似。你可以使用 guard case let 和 guard case let … ,确保内容与模式和条件匹配,否则退出,还以上面的例子为例:
let m = Media.movie(title: "Captain America: Civil War", director: "Russo Brothers", year: 2016)
guard case let Media.movie(_, _, year) = m, year < 1888 else {
print("It is ok!")
return
}
// It is ok!
for case let
for case let 让你有条件的遍历一个集合对象。例子如下:
let mediaList: [Media] = [
.book(title: "Harry Potter and the Philosopher's Stone", author: "J.K. Rowling", year: 1997),
.movie(title: "Harry Potter and the Philosopher's Stone", director: "Chris Columbus", year: 2001),
.book(title: "Harry Potter and the Chamber of Secrets", author: "J.K. Rowling", year: 1999),
.movie(title: "Harry Potter and the Chamber of Secrets", director: "Chris Columbus", year: 2002),
.book(title: "Harry Potter and the Prisoner of Azkaban", author: "J.K. Rowling", year: 1999),
.movie(title: "Harry Potter and the Prisoner of Azkaban", director: "Alfonso Cuarón", year: 2004),
.movie(title: "J.K. Rowling: A Year in the Life", director: "James Runcie", year: 2007),
.website(urlString: "https://en.wikipedia.org/wiki/List_of_Harry_Potter-related_topics")
]
print("Movies only:")
for case let Media.movie(title, _, year) in mediaList {
print(" - \(title) (\(year))")
}
/*
Movies only:
- Harry Potter and the Philosopher's Stone (2001)
- Harry Potter and the Chamber of Secrets (2002)
- Harry Potter and the Prisoner of Azkaban (2004)
- J.K. Rowling: A Year in the Life (2007)
*/
for case let where
使用 for case let where 为 for case let 创建从属条件,例子如下:
print("Movies by C. Columbus only:")
for case let Media.movie(title, director, year) in mediaList where director == "Chris Columbus" {
print(" - \(title) (\(year))")
}
/*
Movies by C. Columbus only:
- Harry Potter and the Philosopher's Stone (2001)
- Harry Potter and the Chamber of Secrets (2002)
*/
⚠️注意:使用 for … where 而不带 case 模式匹配依然是符合 Swift 语法规则的,这样写也是 OK 的:
for m in listOfMovies where m.year > 2000 { … }
参考: