Swift 模式匹配

4,593 阅读4分钟

原文地址

模式匹配是 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 { … }

参考:

  1. appventure.me/guides/patt…
  2. docs.swift.org/swift-book/…
  3. alisoftware.github.io/swift/patte…