可选类型里的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里。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容