可选类型里的Map和FlatMap有多强大

译者注:这个是16年的一篇老文,在深入探究Array的flatmap操作时偶然发现。直到今天,其中的内容可能也不是所有人都了解。由此可见,我们比先驱者接受知识的时间晚了一到两年。共勉!
如果对文章有疑惑,可以去看看Swift可选类型的源代码,github上有,并不复杂,为了不喧宾夺主,这里不做分析。

原文似乎已经打不开了。这里是掘金的转载。

总感觉在学习Swift的过程中,某些宝藏被遗漏了,而这些被遗漏的宝藏可以让我们以一种更舒适的方式使用可选类型。有那么一瞬间,我发现真的有宝藏被我遗漏了。这个宝藏就是Swift可选类型里的Map和FlatMap函数,不是数组里的那个Map和FlatMap。官方的Swift指南里没有提到这个,也找不到相关的实例代码和教程。我也问过一些Swift程序员,都不知道这个。鉴于Swift的这个功能可以让大家写出更优雅的代码,我决定跟大家分享一下我的经验。

如果你不知道map和flatmap函数,接着往下看。
如果你已经了解过了,那么我也希望我列举的这些有用的,真实的使用实例能给你带来新的启发。

map和flatmap都做了啥?

先看一个简短的实例代码,了解这两个函数怎么用。如果你知道怎么用,那就直接跳过。

如果一个可选类型有值,map会获取这个值,经过map的闭包处理变为另外一个值,如果这个可选类型的值为nil,那么不会执行map闭包,而是直接返回nil。下面是一个最简单的例子,可以在Playground里跑一下看看:

var value: Int? = 2
var newValue = value.map { $0 * 2 }
// newValue is now Optional(4)

value = nil
newValue = value.map { $0 * 2 }
// newValue is now nil

咋一看,蛮奇怪,可选类型也可以调用函数。对于可选类型,我们不是都要先解包再使用才符合套路吗?现在看来的确不用先解包。因为,map是可选类型的一个函数,而不是可选类型里面值的函数!

flatmap和map如出一辙,除了一点,map闭包不能return nil,而flatmap可以。再看一个简单的例子:

var value: Double? = 10
var newValue: Double? = value.flatMap { v in
    if v < 5.0 {
        return nil
    }
    return v / 5.0
}
// newValue is now Optional(2)

newValue = newValue.flatMap { v in
    if v < 5.0 {
        return nil
    }
    return v / 5.0
}
// now it's nil

这里要是用map代替flatmap,编译都不会通过。

什么时候使用?

很多时候,你会用三目运算符来检查一个可选类型,如果可选类型不为nil,那么返回其中的值,否则返回nil,这种情况,用两个map函数中一个可能更好。是不是对下面的代码很亲切?也许是时候复查一下你的代码,做些改变了。

var value: Int? = 10
var newValue: Int? = value != nil ? value! + 10 : nil 
// or the other way around:
var otherValue: Int? = value == nil ? nil : value! + 10

强制解包操作都用上了,显然已经不那么对了。所以还是用map函数吧。

当然,你会狡辩说,我也可以不用强制解包,用if let和guard也可以轻松搞定:

func addTen(value: Int?) -> Int? {
  if let value = value {
    return value + 10
  }
  return nil
}

func addTwenty(value: Int?) -> Int? {
  guard let value = value else {
    return nil
  }
  return value + 20
}

还是和上面三目运算符做法一样,换了个写法而已,,用map终究更优雅。

使用map的真实场景

现在我们看看用一种聪明的方式使用map函数的场景,这种方式你可能一下子想不明白。传进去一个函数,这个函数的唯一参数就是这个可选类型里面的值,这就算是完全利用上了map函数的功能。下面所有的例子,都有一般写法和map写法两种对照。

日期格式化

一般写法:

var date: NSDate? = ...
var formatted: String? = date == nil ? nil : NSDateFormatter().stringFromDate(date!)

map写法:

var date: NSDate? = ...
var formatted:  String? =  date.map(NSDateFormatter().stringFromDate)
Segue from cell in UITableView

UITableView里Segue通过cell跳转

一般写法:

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if let cell = sender as? UITableViewCell, let indexPath = tableView.indexPathForCell(cell) {
    (segue.destinationViewController as! MyViewController).item = items[indexPath.row]
  }
}

map写法:

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if let indexPath = (sender as? UITableViewCell).flatMap(tableView.indexPathForCell) {
    (segue.destinationViewController as! MyViewController).item = items[indexPath.row]
  }
}

String字面量里的值

一般写法

func ageToString(age: Int?) -> String {
    return age == nil ? "Unknown age" : "She is (age!) years old"
}

map写法

func ageToString(age: Int?) -> String {
    return age.map { "She is ($0) years old" } ?? "Unknown age"
}

上面的例子里,应该在 (age!)和 ($0) 前面加上反斜杠,但不幸的是, 反斜杠会破坏WordPress的格式

本地化字符串

一般写法:

let label = UILabel()
func updateLabel(value: String?) {
  if let value = value {
    label.text = String.localizedStringWithFormat(
      NSLocalizedString("value %@", comment: ""), value)
  } else {
    label.text = nil
  }
}

map写法:

let label = UILabel()
func updateLabel(value: String?) {
  label.text = value.map { 
    String.localizedStringWithFormat(NSLocalizedString("value %@", comment: ""), $0) 
  }
}

通过rawValue构造枚举,这个枚举失败时返回缺省值

一般写法:

enum State: String {
    case Default = ""
    case Cancelled = "CANCELLED"

    static func parseState(state: String?) -> State {
        guard let state = state else {
            return .Default
        }
        return State(rawValue: state) ?? .Default
    }
}

map写法:

enum State: String {
    case Default = ""
    case Cancelled = "CANCELLED"

    static func parseState(state: String?) -> State {
        return state.flatMap(State.init) ?? .Default
    }
}

在数组中找到某项

Item定义:

struct Item {
    let identifier: String
    let value: String
}

let items: [Item]

一般写法:

func find(identifier: String) -> Item? {
    if let index = items.indexOf({$0.identifier == identifier}) {
        return items[index]
    }
    return nil
}

map写法:

func find(identifier: String) -> Item? {
    return items.indexOf({$0.identifier == identifier}).map({items[$0]})
}

json字典构造对象

结构体(或者类):

struct Person {
    let firstName: String
    let lastName: String

    init?(json: [String: AnyObject]) {
        if let firstName = json["firstName"] as? String, let lastName = json["lastName"] as? String {
            self.firstName = firstName
            self.lastName = lastName
            return
        }
        return nil
    }
}

一般写法:

func createPerson(json: [String: AnyObject]) -> Person? {
    if let personJson = json["person"] as? [String: AnyObject] {
        return Person(json: personJson)
    }
    return nil
}

map写法:

func createPerson(json: [String: AnyObject]) -> Person? {
    return (json["person"] as? [String: AnyObject]).flatMap(Person.init)
}

结论

map和flatmap可以发挥不可想象的能量,让你的代码更优雅。希望我所列举的这些例子,能让你在合适的时候合适的地方使用上他们,并从中获益。

如果你也有一些聪明的方式使用map和flatmap的例子,请在评论里告诉我,我会添加到上面的list里。

你可能感兴趣的:(可选类型里的Map和FlatMap有多强大)