模式匹配是swift语言的亮点,如果运用得当,能有效提升代码的 逼格。比方说if case,guard case,for case。如过你还没有使用过它们,或者不确信它们是否可行,不妨试试下面的内容。
if case
case let x = y 表达式可以完成检查 y 是否匹配模式 x。
表达式 if case let x = y { … } 则等同于表达式 switch y { case let x: … } 。前者是后者的一种简写,switch适用于多分支的模式匹配,而if case则适合于单条case的匹配情况。
比方说我们有这样一个枚举:
enum Media {
case Book(title: String, author: String, year: Int)
case Movie(title: String, director: String, year: Int)
case WebSite(urlString: String)
}
如果使用switch进行匹配的话:
let m = Media.Movie(title: "Captain America: Civil War", director: "Russo Brothers", year: 2016)
switch m {
case let Media.Movie(title, _, _):
print("This is a movie named \(title)")
default: ()
}
我们只用到了单分支匹配,但需要描述 switch,default,啰嗦不?所以要:变
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)")
}
这样写有没有感觉就像理了头发,清爽了许多,心情瞬间美好起来?其实不只是模式匹配,完全可以在混合一下条件表达式:
if case let Media.Movie(_, _, year) = m where year < 1888 {
print("Something seems wrong: the movie's year is before the first movie ever made.")
}
通过if case表达式,我们几乎把switch的所有本事都搬来了,愉快。
guard case
从模式匹配的角度来看,guard case与if case的语法结构是完全相同的,它们的区别仅仅体现在guard关键字与if关键字的区别上。直接看一个例子。
enum NetworkResponse {
case Response(NSURLResponse, NSData)
case Error(NSError)
}
func processRequestResponse(response: NetworkResponse) {
guard case let .Response(urlResp, data) = response,
let httpResp = urlResp as? NSHTTPURLResponse
where 200..<300 ~= httpResp.statusCode else {
print("Invalid response, can't process")
return
}
print("Processing \(data.length) bytes…")
}
for case
把for、case这两个关键字联合起来,能够让我们在迭代集合的时候,进行模式匹配,这感觉就好像我们在for迭代中,使用了if case。我们先来创建一个Media类型的数组,数组中包括电影和图书。
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")
]
现在我们来完成这样一个任务,我们把集合中所有的电影信息打印出来:
for case let Media.Movie(title, _, year) in mediaList {
print(" - \(title) (\(year))")
}
如果配合上where子句,我们会得到更加强大的能力,比方说我们只打印某一位导演的电影。
for case let Media.Movie(title, director, year) in mediaList where director == "Chris Columbus" {
print(" - \(title) (\(year))")
}
百尺竿头再进一步
我们现在扩展一下Media,让它可以描述title和种类,我们用extension关键字。
extension Media {
var title: String? {
switch self {
case let .Book(title, _, _): return title
case let .Movie(title, _, _): return title
default: return nil
}
}
var kind: String {
switch self {
case .Book: return "Book"
case .Movie: return "Movie"
case .WebSite: return "Web Site"
}
}
}
现在我们打印一下mediaList中所有元素的title和kind
for case let (title?, kind) in mediaList.map({ ($0.title, $0.kind) })
where title.hasPrefix("Harry Potter") {
print(" - [\(kind)] \(title)")
}
这个代码的可读性并不是很高,我们先来简单解释一下上面这个代码片段
- 首先是map函数将 mediaList 映射成一个[(String?,String)]类型的数组。
- 然后我们使用case let 进行了title 与 kind的值绑定,因为我们指明了title?所以要求数组元素中必须包含title
- 最后使用where子句进行条件表达式过滤。
因为代码的可读性决定了代码了可维护性,所以写出容易阅读的代码是至关重要的,我们其实可以使用guard关键字,让上面的这个段代码变得更加清晰简洁(虽然长度会略长一些)。
for media in mediaList {
guard let title = media.title else {
continue
}
guard title.hasPrefix("Harry Potter") else {
continue
}
print(" - [\(media.kind)] \(title)")
}
希望if case可以更多的出现在我们的代码中,来让我们的代码更优美。
本文参考