模式匹配第四彈:if case,guard case,for case

做者:Olivier Halligon,原文連接,原文日期:2016-05-16
譯者:walkingway;校對:Cee;定稿:numbbbbbgit

如今咱們來從新回顧下前三彈模式匹配的各類語法 第一彈第二彈第三彈,第四彈是本系列的最後一篇文章,本章會教你們使用 if case letfor case where 等一些高級語法,讓咱們拭目以待吧!github

本篇文章會結合本系列前三篇文章提到的語法,而後將它們應用在一些更先進表達式中。swift

這篇文章是模式匹配系列文章的最後一部分,你能夠在這裏閱讀全部內容:第一彈第二彈第三彈第四彈設計模式

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)")
}

改用 switch 後更冗長的版本:this

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 letwhere 從句組合在一塊兒用:url

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.")
}

這種方式能夠組合成一個至關強大的表達式,而改用 switch 實現可能會變得很是複雜,須要寫不少行代碼來檢測那一個特定的 case。

guard case let

固然,guard case let 相似於 if case let,你可使用 guard case letguard case let … where … 來確保匹配一個模式或一個條件,而當沒法匹配模式或知足條件時就退出。

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

forcase 組合在一塊兒也能讓你有條件地遍歷一個集合對象。使用 for case … 語義上相似於 for 循環,並且將它整個循環體封裝在了 if case 的結構之中:它只會遍歷、處理那些模式匹配了的元素。

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))")
}
  
/* Output:
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 where

for case 增長一個 where 從句,能使其變得更增強大:

print("Movies by C. Columbus only:")
for case let Media.Movie(title, director, year) in mediaList where director == "Chris Columbus" {
  print(" - \(title) (\(year))")
}

/* Output:
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 { … }

這裏沒有使用模式匹配(沒有 case 或 ~=),所以有點超出了本系列的主題範圍,可是這種寫法是徹底有效的,並且這種構造也很是有用---特別是避免了將一個巨大的判斷邏輯 if 結構(或是一個 guard … else { continue })封裝在 for 的循環體內。

將它們組合起來使用

如今咱們終於要迎來這系列文章的大結局了:把咱們以前所學從頭至尾串聯起來(包括一些咱們在以前章節學習到的相似 x? 這種語法糖):

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 {
    /* Remember part 1 where we said we can omit the `(…)` 
    associated values in the `case` if we don't care about any of them? */
    switch self {
    case .Book: return "Book"
    case .Movie: return "Movie"
    case .WebSite: return "Web Site"
    }
  }
}

print("All mediums with a title starting with 'Harry Potter'")
for case let (title?, kind) in mediaList.map({ ($0.title, $0.kind) })
  where title.hasPrefix("Harry Potter") {
    print(" - [\(kind)] \(title)")
}

上面的代碼可能看上去有點複雜,咱們先拆分一下:

  • 使用 map 函數將 Array<Media> 類型的數組 mediaList 轉換成一個包含元組 [(String?, String)] 的數組,而其中的元組包含兩個元素:第一個是標題(String? 類型),第二個是元素的種類(String 類型)

  • 它只當 title? 匹配時整個表達式纔會匹配──還記得第三彈的那個語法糖嗎:「當 switch 處理一個可選值 x? 時,你能夠識別問號標記的可選值」,所以這裏的 title? 至關於 .Some(title),它是不會匹配 title 爲 nil 的狀況的(譯者注:至於爲何要寫成 title? 上一彈也有說明:由於後面與之匹配的是一個可選值(mediaList.map(...) 的 title),匹配類型要一致,不然會報錯。)──所以匹配的結果是剔除全部 $0.titlenilmedia(也就是 title 爲 Optional.None)──最終剩下的 media 中不包括 WebSite 類型,由於它沒有 title

  • 而後再進一步去遍歷 media,判斷他們的 title 是否知足 title.hasPrefix("Harry Potter") 條件

最後這段代碼將遍歷每個 medium,篩選出那些以 「Harry Potter」 開頭的,在這一過程當中將丟棄那些沒有標題的,好比 WebSite,還有那些標題不以 "Harry Potter" 開頭的 medium,這也包括做者 J.K.羅琳的記錄片。

最終的輸出結果以下,只有和 Harry Potter 相關的書籍和電影:

All medium with a title starting with 'Harry Potter'
  • [Book] Harry Potter and the Philosopher's Stone

  • [Movie] Harry Potter and the Philosopher's Stone

  • [Book] Harry Potter and the Chamber of Secrets

  • [Movie] Harry Potter and the Chamber of Secrets

  • [Book] Harry Potter and the Prisoner of Azkaban

  • [Movie] Harry Potter and the Prisoner of Azkaban

若是不使用模式匹配、where 從句、或是前面提到的各類語法糖,代碼寫出來多是這樣的:

print("All mediums with a title and starting with 'Harry Potter'")
for media in mediaList {
  guard let title = media.title else {
    continue
  }
  guard title.hasPrefix("Harry Potter") else {
    continue
  }
  print(" - [\(media.kind)] \(title)")
}

有些人可能以爲這種寫法可讀性更好,但你沒法否定 for case let (title?, kind) in … where … 確實強大,能夠在小夥伴面前裝個逼,並且能夠將循環 + 模式匹配 + where 從句組合起來是使用 ✨。

結論

「模式匹配」系列文章到此就所有結束了,但願你能喜歡它,並真正學到了一些有趣的東西 ?。

下一篇文章我將聚焦 Swift 的設計模式與架構,而再也不是語言自己的語法了。

? 若是關於 Swift 你有什麼特別想要從我這裏瞭解到的,請不要猶豫,直接在 Twitter 上聯繫我吧,個人下一篇文章的靈感極可能就來自於大家的建議。

模式匹配和參數的順序顛倒會致使語法錯誤。爲了方便記憶,想像一下 switchcase let Media.Movie(…) 的順序,他們是一致的。這樣你就知道應該是 if case let Media.Movie(…) = m ,而不是 if case let m = Media.Movie(…),後者是徹底不會編譯的。和 switch 中所作的同樣,將 case 和模式((Media.Movie(title, _, _))放在一塊兒,而不是與變量(m)在一塊兒。
本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 http://swift.gg

相關文章
相關標籤/搜索