总感觉在学习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里。