作者:Olivier Halligon,原文链接,原文日期:2015-09-20
译者:我偏笑;校对:pmst;定稿:shanks

本系列的第一篇文章中,介绍了如何避免对可选类型强制解包以及拯救“小马”🐴 。在第二部分中,我们将会精简前文代码来让它看起来更有”雨燕风”(Swift-er : Swift编程语言风格),同时向你介绍 map()flatMap() 方法。

今天这篇文章我们将要讨论数组的 map()flatMap() 方法。

前情提要1

友情提示: 以下为前文留下的代码:

class ListItem {
var icon: UIImage?
var title: String = ""
var url: NSURL!

static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
guard let nonNilJsonData = jsonData,
let json = try? NSJSONSerialization.JSONObjectWithData(nonNilJsonData, options: []),
let jsonItems = json as? Array<NSDictionary>
else {
// 倘若JSON序列化失败,或者转换类型失败
// 返回一个空数组结果
return []
}

var items = [ListItem]()
for itemDesc in jsonItems {
let item = ListItem()
if let icon = itemDesc["icon"] as? String {
item.icon = UIImage(named: icon)
}
if let title = itemDesc["title"] as? String {
item.title = title
}
if let urlString = itemDesc["url"] as? String, let url = NSURL(string: urlString) {
item.url = url
}
items.append(item)
}
return items
}
}

本文的目标是使用更多“雨燕风”的模式和语法,使得代码看起来更棒并且简洁。

map()方法介绍

map()Array 提供的方法,通过接收一个函数作为传入参数,对数组中每个元素进行函数变换得到新的结果值。这样只需要提供X->Y的映射关系,就能将数组[X]变换到新数组[Y],而无需创建一个临时可变数组(注:即上面代码中的items变量)。

本例中,我们不再像之前一样用 for 来做循环,而是对 jsonItems(JSON数据:存储于类型为字典的数组中)使用 map() 方法,并传入一个变换函数(闭包),将每个 NSDictionary 类型数组元素转换成我们所需的 ListItem 实例:

return jsonItems.map { (itemDesc: NSDictionary) -> ListItem in
let item = ListItem()
if let icon = itemDesc["icon"] as? String {
item.icon = UIImage(named: icon)
}
if let title = itemDesc["title"] as? String {
item.title = title
}
if let urlString = itemDesc["url"] as? String, let url = NSURL(string: urlString) {
item.url = url
}
return item
}

这看起来只是个很小的改动,但是它让我们专注于怎样把 NSDictionary 转化成 ListItem,毕竟这是解决问题的核心。更为重要的是,避免了像在 ObjC 里做的那样,新建一个中间数组。我们应该尽可能地避免这种情况发生。

错误数据

目前代码还存在一个问题:即便输入的数据是不可用的,我们依然创建了一个 ListItem 实例(并返回添加到结果数组jsonItems当中)。所以,倘若某些 NSDictionary是无效的,最终的输出数组中,就会添加一些毫无意义的ListItem()空实例。
更重要的是,我们仍在杀死一些小马 🐴 。当我们使用NSURL!时,代码允许我们创建那些没有 NSURLListItem() 实例(我们没有一个有效的url键值,所以访问item.url不起作用),当我们访问无效的NSURL!时,程序将会崩溃。

为了解决这个问题,我们对变换函数稍加修改,当输入值无效时,返回一个值为nilListItem,这比返回一个错误或无内容的ListItem更为合适。

return jsonItems.map { (itemDesc: NSDictionary) -> ListItem? in
guard …/* condition for valid data */else { return nil }
let realValidItem = ListItem()
/* fill the ListItem with the values */
return realValidItem
}

但是如果 jsonItems.map 里面传入的函数参数类型为 NSDictionary -> ListItem?,最后我们得到的是一个 [ListItem?] 数组,那些原来是不可用 NSDictionary 的位置就被我们替换成了 nil。比原来要好一些了,但还不够。

使用flatMap()

这个时候就轮到 flatMap() 来救场了。

flatMap()map() 相似,但 flatMap() 用的是 T->U? 变换而不是 T->U 转化,而且倘若变换后的数组元素值为 nil2,则不会被添加到最后的结果数组里面。

从语法上,你可以这么理解,flatMap 就是先使用 map处理数组,接着将结果数组“压平”(顾名思义),也就是从输出数组里剔除值为nil的元素。

通过flatMap方法改写后的实例代码如下:

return jsonItems.flatMap { (itemDesc: NSDictionary) -> ListItem? in
guard let title = itemDesc["title"] as? String,
let urlString = itemDesc["url"] as? String,
let url = NSURL(string: urlString)
else { return nil }
let li = ListItem()
if let icon = itemDesc["icon"] as? String {
li.icon = UIImage(named: icon)
}
li.title = title
li.url = url
return li
}

现在我们只返回所有键都存在3并有效的 ListItem对象(保证 NSURL不为nil)。否则执行guard语句,返回nil值通知flatMap不要将这些无效元素添加到返回结果数组中。

这样做就更好更安全了,对吧?,我们解决了数据异常的问题,当有错误输入时候,避免了无效的ListItem项添加到数组当中。

结论

我们仍然有很多工作要做,但是今天就先做这些吧(让我们为本系列文章的下一篇准备一下材料!)

在这篇文章里面,我们学到了怎么用map或者flatMap来替换掉for循环,确保即便输入数据是不可用的的情况下,输出数组也不会出问题。这确实已经算是很大的进步了。

在下一篇文章里,将介绍如何用结构体(struct)重写ListItem类有助于探索 mapflatMap 的其它用法 – 尤其是在处理 Optionals 的时候。

同时,希望你花点时间来深入了解一下 map()flatMap() 在数组上的应用,我知道你第一次学的时候可能觉得它们很复杂,但是一旦你学会了,你什么时候都会想用它们。

1: 请脑补一些作秀节目的用词

2: flatMap还有其他一些作用。比如把一个二维数组变换为一维数组,比如,[[T]] -> [T]。但是在这里,我们只需要关注在如何使用 T->U? 的变换把 [T] 变成 [U]。

3: 注意到我们的代码中,允许NSDictionary没有icon键,这意味着我们认为一个ListItem可以不需要有任何icon。但是其他键是必须的。

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg

文章目录
  1. 1. 前情提要1
  2. 2. map()方法介绍
    1. 2.0.1. 错误数据
  • 3. 使用flatMap()
  • 4. 结论