在本系列文章的第一篇中,我们知道了怎样避免对可选类型强制拆包,尽量少使用 !
也避免了程序崩溃。在第二部分,我将会精简我们的代码来让它看起来更 “Swift”,同时向你介绍 map()
和 flatMap()
方法。
本系列文章的第一篇里
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 {
// If we Failed to unserialize the JSON or that JSON wasn't an NSArray,
// then bail early with an empty array
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
}
}
我们的目的是使用更多更 “Swift” 的模式和语法来让我们的代码变得更简洁。
map()函数的介绍
map()
是 Array
提供的方法,它接收一个函数作为参数,旧数组中的每个元素都会被拿去执行这个函数而变成新数组的对应元素,这是一种让数组从 [X]
转化为 [Y]
的方式,你需要提供的就是 X -> Y
的转化方式,而不必新建一个临时数组。
在我们例子里,我们不再像之前一样用 for
来做循环,而是对 jsonItems
– 我们 NSDictionary
类型的 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
实例。这样的话,如果我们的某些 NSDictionary
是不可用的,最后的输出数组里,就会有很多无意义的无内容的 ListItem()
实例,其中也就不包含可用的 NSURL
。
更糟的是,代码允许我们创建那些没有可用 NSURL
的 ListItem()
实例,而我们在 NSURL!
中使用了 !
,这样一旦执行了 NSURL!
这样的强制拆包,我们的程序就得崩。
为了解决这个问题,如果我们接收到的输入是不可用的,就要返回一个 nil
,这比返回一个无内容的或者错误的 ListItem
来导致拆包 NSURL
时崩溃要好得多。
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
转化,而且如果转化出的数组元素是 nil
的话,就不会被添加到最后的结果数组里面。
在语法上,你可以这么理解,flatMap
就是你先使用 map
然后把结果数组“压平”(毕竟函数名就是这个意思),也就是从输出数组里去掉那些 nil
。
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
}
现在我们只返回所有键都存在的 ListItem
(也就是我们保证 NSURL
是不为空的)。那些不可用的元素早在我们执行 guard
语句通知 flatMap
之后,就被从输出数组中去掉了。
这样做就更好更安全了吧,我们解决了错误输入数据和得到无内容实例的问题。
结论
我们仍然有很多工作要做,但是今天就先做这些吧(让我们为本系列文章的下一篇准备一下材料!)
在这篇文章里面,我们学到了怎么用 map
或者 flatMap
来替换掉 for
循环,我们成功保证了即便输入数据是不可用的的情况下,我们的输出数组也不会出问题。这确实已经算是很大的进步了。
在下一篇文章里,我们将学到把我们的 ListItem
转化成一个 struct
之后会带来什么样的好处,并且探索 map
和 flatMap
的其它用法 – 尤其是在处理 Optionals
的时候。
同时,我们希望你花点时间来深入了解一下 map()
和 flatMap()
在数组上的应用,我知道你第一次学的时候可能觉得它们很复杂,但是一旦你学会了,你什么时候都会想用它们。