Swift 编程思想 Part 4:map all the things!

2015-10-22  837

作者:Olivier Halligon,原文链接,原文日期:2015-10-11
译者:ray16897188;校对:Prayer;定稿:numbbbbb

系列文章地址:

在本系列之前的文章中我们学到了如何使用mapflatMap来操作数组(arrays)。今天我们继续研究如何对可选类型(Optionals)以及很多其他类型使用mapflatMap

数组 vs. 可选类型

回顾一下,学完前面的文章后我们已经知道,Array<T>对应的map()flatMap()函数签名是:

1
2
3
// 作用在Array<T>上的方法
map( transform: T -> U ) -> Array<U>
flatMap( transform: T -> Array<U> ) -> Array<U>

意思是你可以用一个给定的transform: T->U将一个元素类型是T的数组转换成一个元素类型是U的数组。对Array<T>调用map( transform: T->U )方法就会返回一个Array<U>,就这么简单。

嗯,不出意外,对于Optional<T>来说,map()flatMap()的函数签名十分类似:

1
2
3
// 作用在Optional<T>上的方法
map( transform: T -> U ) -> Optional<U>
flatMap( transform: T -> Optional<U> ) -> Optional<U>

是不是很像?

作用在可选类型上的 map()

那么map方法到底对Optional<T>类型(也叫做T?)做了什么?

其实很简单:和作用在Array<T>上的一样,map方法将Optional<T>中的内容取出来,用指定的transform: T->U方法做出转换,然后把结果包装成一个新的Optional<U>

如果细想一下,这和Array<T>.map做的事情十分相似:这个方法对Array<T>(与之相应的是Optional<T>)中的每个元素使用transform函数转换,并将转换过的值封装在一个新的Array<U>中(与之相应的是Optional<U>),作为结果返回。

回到我们的例子

那么这对我们一直在做的示例代码有什么帮助?

我们最新版代码中,有一个String?类型的itemDesc["icon"],我们当时想把它转换成一个UIImage;但是UIImage(named:)要求传入一个String型的参数,而不是String?型,所以我们需要在可选型中确实有值时(非nil)将内部的String值传入。

一种解决方案是使用可选绑定(Optional Binding):

1
2
3
4
5
6
let icon: UIImage?
if let iconName = itemDesc["icon"] as? String {
icon = UIImage(named: iconName)
} else {
icon = nil
}

但是对于一个如此简单的操作来说代码量太大。

之前的一个例子中我们用了另外一种(很不优雅的)方式,使用nil-联合操作符??

1
2
let iconName = itemDesc["icon"] as? String
let icon = UIImage(named: iconName ?? "")

这么做是可以,但是之所以能够成功,是因为当iconNamenil时,我们实际上是使用了UIImage(named: "")的初始化方法,这个初始化方法在传入空字符串时,会返回nil。但是这样的解决办法不是很好,因为我们是依赖于该初始化方法的特性(传入空字符串时,会返回nil)来实现的。

来用 map 吧

那么为什么不用map呢?本质上,我们是想要在Optional<String>不是nil的时候将其解包,把里面的值转换成一个UIImage对象然后把这个UIImage返回,这不就是一个绝佳的用例么?

试试看:

1
2
let iconName = itemDesc["icon"] as? String
item.icon = iconName.map { imageName in UIImage(named: imageName) }

等会儿…. 编译不通过。能猜出为什么吗?

哪儿有问题?

上面的代码中的问题是UIImage(named: …)也返回一个可选类型:如果对给定的name没有相应的图片,就不能创建出一个UIImage,所以这种情况下该初始化方法为可失败的(failable),并返回nil,是完全合理的。

于是问题就在于我们给map的这个闭包用一个String作为参数而返回…一个UIImage?类型——因为图片的初始化方法是可失败的,会返回nil。再看一下map方法的签名,它想要的是一个T->U类型的闭包,这个闭包会返回一个U?类型。我们的例子中,U代表UIImage?的话,整个map表达式会返回一个U?类型,也就是…一个UIImage??类型…是的,一个双重可选类型,吓死宝宝了!

flatMap() 来帮忙了

flatMap()map类似,但是做的是一个T->U?的转换(不是T->U),它把结果“扁平化(顾名思义)”成一个单重的可选类型。这恰恰就是我们所需要的!

1
2
let iconName = itemDesc["icon"] as? String
item.icon = iconName.flatMap { imageName in UIImage(named: imageName) }

实际中flatMap做了如下工作:

  • 如果iconNamenil的话,它就直接返回nil(但返回类型还是UIImage?)
  • 如果iconName不是nil,它就把transform作用到iconName的实际的值上,尝试用这个String创建一个UIImage并将结果返回——结果本身已经是一个UIImage?类型,因此如果UIImage初始化方法失败的话,返回结果就是nil

简而言之,item.icon只会在itemDesc["icon"] as? String非空、并且UIImage(named: imageName)初始化方法成功的情况下才是一个非空值。

和使用??欺骗初始化方法相比,这么做更好,更地道。

把 init 当闭包来用

更进一步,由于现在 Xcode 7 可以通过类型的.init属性暴露该类型的构造器(constructors),上面的代码还能写的更加紧凑。

这意味着UIImage.init本质上就已经是一个接收String并返回UIImage?的方法了,所以我们可以把它直接当成参数来调用flatMap,不用把它再包进一个闭包里!

1
2
let iconName = itemDesc["icon"] as? String
item.icon = iconName.flatMap(UIImage.init)

哇哦!太魔幻了!

好了,有人说这么写很难读懂,为了让代码更明了更清晰,在这里还是更喜欢用一个显式闭包。但是这只是关乎个人偏好,并且知道这么做可行也是好事。

最终的Swift代码

下面就是将本课所学应用到之前代码里的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct ListItem {
var icon: UIImage?
var title: String
var url: NSURL static func listItemsFromJSONData(jsonData: NSData?) -> [ListItem] {
guard let jsonData = jsonData,
let json = try? NSJSONSerialization.JSONObjectWithData(jsonData, options: []),
let jsonItems = json as? Array<NSDictionary> else { return [] } 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 iconName = itemDesc["icon"] as? String
let icon = iconName.flatMap { UIImage(named: $0) }
return ListItem(icon: icon, title: title, url: url)
}
}
}

回头看一眼我们的 ObjC 代码

花一点儿时间比较一下我们最终的 Swift 代码和最开始的ObjC代码。我们着实改了很大一部分内容。

如果你仔细看一下 ObjC 和 Swift 代码,会发现 Swift 的代码量并不是那么少(ObjC 是 5+15 LoC1,对比 Swift 的 19 LoC),但是安全性高了太多

尤其是我们使用的guardtry?as?会迫使我们去检查所有类型是否都如所期,ObjC 代码不会关心这些,因此可能崩溃

Swift 编程思想 Part 4:map all the things!的更多相关文章

  1. Swift 编程思想 阅读笔记

    Swift 编程思想,第一部分:拯救小马html, body {overflow-x: initial !important;}.CodeMirror { height: auto; } .CodeM ...

  2. [Hadoop入门] - 1 Ubuntu系统 Hadoop介绍 MapReduce编程思想

    Ubuntu系统 (我用到版本号是140.4) ubuntu系统是一个以桌面应用为主的Linux操作系统,Ubuntu基于Debian发行版和GNOME桌面环境.Ubuntu的目标在于为一般用户提供一 ...

  3. Java编程思想(11~17)

    [注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第十一章 持有对象 11.1 泛型和类型安全的容器>eg: List<St ...

  4. Java编程思想 (1~10)

    [注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第一章 对象导论 1.万物皆对象2.程序就是对象的集合3.每个对象都是由其它对象所构成 ...

  5. Java编程思想总结笔记The first chapter

    总觉得书中太啰嗦,看完总结后方便日后回忆,本想偷懒网上找别人的总结,无奈找不到好的,只好自食其力,尽量总结得最好. 第一章  对象导论 看到对象导论觉得这本书 目录: 1.1 抽象过程1.2 每个对象 ...

  6. Java编程思想读书笔记(一)【对象导论】

    2018年1月7日15:45:58 前言 作为学习Java语言的经典之作<Java编程思想>,常常被人提起.虽然这本书出版十年有余,但是内容还是很给力的.很多人说这本书不是很适合初学者,我 ...

  7. Java编程思想(后)

    Java编程思想(后) 持有对象 如果一个程序只包含固定数量的且其生命期都是已知的对象,那么这是一个非常简单的程序. Java中的库基本类型: List, Set, Queue和Map --- 称为集 ...

  8. Java编程思想(前十章)

    Java编程思想 有C++编程基础的条件下, 前10章可以快速过一下,都是基本语法,不需要花太多时间. 着重中后段的一些章节,类型信息.泛型.容器.IO.并发等. 中文翻译版 阅读地址 对于一个架构师 ...

  9. javaWeb中MVC的编程思想示例

    没有学习MVC之前我只写了一个Servlet类(Note_List.java),分层之后,我将这个类分成了5个类(NoteDao.java,,NoteDaoImpl.java,,NoteService ...

随机推荐

  1. CTP Release() 的注意问题

    测试时发现CThostFtdcMdSpi有个比较严重的问题,就是使用Release()退出清理对象时 会出现死机,并且频率很高,怎样解决? 答:请参考以下代码的释放顺序. template <c ...

  2. 51nod1267(双指针)

    题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1267 题意:中文题诶- 思路:双指针 求a+b+c+d=0,令 ...

  3. su和sudo命令的用法

    为了安全起见,尽量不要用root用户去做所有事情,因为一旦执行了错误的命令,可能会直接导致系统崩溃. 一.su命令 su 命令可以解决切换用户身份的需求,使得当前用户在不退出登录的情况下,切换到其他用 ...

  4. 基于IDEA采用springboot+Mybatis搭建ssm框架简单demo项目的搭建配置流程

    一.通过对比可以原始SSM搭建流程,spring boot省去了大量的配置,极大提高了开发者的效率.原始SSM框架搭建流程见博客: https://www.cnblogs.com/No2-explor ...

  5. plt

    设定X,Y轴的长度以及刻度的方法. import numpy as np import matplotlib.pyplot as plt data = np.arange(0,1.1,0.01) pl ...

  6. vue教程1-初体验

    起步 var vm = new Vue({ // 选项 }) #每个Vue应用都需要通过实例化Vue来实现,语法格式继承原生js <!DOCTYPE html> <html lang ...

  7. AKOJ-2037-出行方案

    链接:https://oj.ahstu.cc/JudgeOnline/problem.php?id=2037 题意: 安科的夏天真是不一般的热,避免炎热,伍学长因此想为自己规划一个校园出行方案,使得从 ...

  8. odoo filter 日期

    <!--日期--> <filter name="before_twodays" string="前天" domain="[('dat ...

  9. Access denied for user ''@'localhost' to database 的一个问题

    $conn = new mysqli("127.0.0.1", 'abc', '', DB_DATABASE); 在提供了用户名的情况下,竟然返回错误 说用户提供的用户为空,非常奇 ...

  10. vue http请求 vue-resource使用方法

    1.安装vue-resource扩展: npm install vue-resource 2.在main.js中引入 import http from 'vue-resource' 3.使用方法 // ...