你在代码中使用if case了吗?

作者: 八条8tiao | 来源:发表于2016-07-25 09:37 被阅读762次

    模式匹配是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可以更多的出现在我们的代码中,来让我们的代码更优美。

    本文参考

    https://www.natashatherobot.com/swift-2-pattern-matching-with-if-case/

    http://alisoftware.github.io/swift/pattern-matching/2016/05/16/pattern-matching-4/

    相关文章

      网友评论

        本文标题:你在代码中使用if case了吗?

        本文链接:https://www.haomeiwen.com/subject/jbygjttx.html