Swift flatMap详解

  • Swift flatMap详解
    • flatMap 的其中一个重载
    • flatMap 的另一个重载

Swift flatMap详解

先看下flatMap的用法

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element] where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]

Map 可以对一个集合类型的所有元素做一个映射操作, 那么 flatMap 跟Map有什么区别呢?

让我们来看一个 flatMap 的用法:

result = numbers.flatMap { $0 + 1 }
// [2,3,4,5]

同样的数组使用 flatMap 进行处理,得到的结果是一样的,那 flatMap 和 map 到底有什么区别呢?
让我们再看一个场景

let numbersCompound = [[1,2,3],[4,5,6]];
var res = numbersCompound.map { $0.map{ $0 + 1 } }
// [[2,3,4], [5,6,7]]
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 1 } }
// [2,3,4,5,6,7]

这里就看出差别了,对于二维数组来说,Map和flatMap的结果就不同了。
相比于Map,flatMap 依然会遍历数组的元素,并对这些元素执行闭包中定义的操作。 但唯一不同的是,它对最终的结果进行了所谓的 “降维” 操作。 本来原始数组是一个二维的, 但经过 flatMap 之后,它变成一维的了。
下面咱们再来看一下 flatMap 的函数定义

func flatMap(transform: (Self.Generator.Element) throws -> T?) -> [T]
func flatMap(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]

flatMap 的其中一个重载

flatMap 有两个重载。 参照我们刚才的示例,我们调用的其实是第二个重载,flatMap 的闭包接受的是数组的元素,但返回的是一个 SequenceType 类型,也就是另外一个数组。 这从我们刚才这个调用中不难看出我们传入给 flatMap 一个闭包 $0.map{ $0 + 1 } , 这个闭包中,又对 $0 调用了 map 方法, 从 map 方法的定义中我们能够知道,它返回的还是一个集合类型,也就是 SequenceType。 所以我们这个 flatMap 的调用对应的就是第二个重载形式。

那么为什么 flatMap 调用后会对数组降维呢? 我们可以从它的源码中窥探一二(Swift 不是开源了吗~)。
文件位置: swift/stdlib/public/core/SequenceAlgorithms.swift.gyb

extension Sequence {//...
public func flatMap(
@noescape transform: (${GElement}) throws -> S
) rethrows -> [S.${GElement}] {var result: [S.${GElement}] = []
for element in self {result.append(contentsOf: try transform(element))
}
return result
}
//...
}

这就是 flatMap 的完整源码了, 它的源码也很简单, 对遍历的每一个元素调用try transform(element)。transform 函数就是我们传递进来的闭包。
然后将闭包的返回值通过result.append(contentsOf:)函数添加到 result 数组中。
那我们再来看一下result.append(contentsOf:)都做了什么, 它的文档定义是这样:
Append the elements of newElements to self.
简单说就是将一个集合中的所有元素,添加到另一个集合。 还以我们刚才这个二维数组为例:

let numbersCompound = [[1,2,3],[4,5,6]];
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 1 } }
// [2,3,4,5,6,7]

flatMap 首先会遍历这个数组的两个元素 [1,2,3] 和 [4,5,6], 因为这两个元素依然是数组, 所以我们可以对他们再进行 map 操作: $0.map{ $0 + 1 }。

这样, 内部的 $0.map{ $0 + 1 } 调用返回值类型还是数组, 它会返回 [2,3,4] 和 [5,6,7]。

然后, flatMap 接收到内部闭包的这两个返回结果, 进而调用 result.append(contentsOf:)将它们的数组中的内容添加到结果集中,而不是数组本身。

那么我们最终的调用结果理所当然就应该是[2,3,4,5,6,7]了。

仔细想想是不是这样呢~

flatMap 的另一个重载

我们刚才分析了半天, 其实只分析到 flatMap 的一种重载情况, 那么另外一种重载又是怎么回事呢:

func flatMap(transform: (Self.Generator.Element) -> T?) -> [T]
从定义中我们看出, 它的闭包接收的是 Self.Generator.Element 类型, 返回的是一个 T? 。 我们都知道,在 Swift 中类型后面跟随一个 ?, 代表的是 Optional 值。 也就是说这个重载中接收的闭包返回的是一个 Optional 值。 更进一步来说,就是闭包可以返回 nil。

我们来看一个例子:

let optionalArray: [String?] = [``"AA"``, nil, ``"BB"``, ``"CC"``]
var optionalResult = optionalArray.flatMap{ $0 }
// ["AA", "BB", "CC"]`

这样竟然没有报错, 并且 flatMap 的返回结果中, 成功的将原数组中的 nil 值过滤掉了。 再仔细观察,你会发现更多。 使用 flatMap 调用之后, 数组中的所有元素都被解包了, 如果同样使用 print 函数输出原始数组的话, 大概会得到这样的结果:

[Optional(``"AA"``), nil, Optional(``"BB"``), Optional(``"CC"``)]

而使用 print 函数输出 flatMap 的结果集时,会得到这样的输出:
["AA", "BB", "CC"]
也就是说原始数组的类型是 [String?] 而 flatMap 调用后变成了 [String]。 这也是 flatMap 和 map 的一个重大区别。 如果同样的数组,我们使用 map 来调用, 得到的是这样的输出:
[Optional(``"AA"``), nil, Optional(``"BB"``), Optional(``"CC"``)]
这就和原始数组一样了。 这两者的区别就是这样。 map 函数值对元素进行变换操作。 但不会对数组的结构造成影响。 而 flatMap 会影响数组的结构。再进一步分析之前,我们暂且这样理解。

flatMap 的这种机制,而已帮助我们方便的对数据进行验证,比如我们有一组图片文件名, 我们可以使用 flatMap 将无效的图片过滤掉:

var imageNames = [``"test.png"``, ``"aa.png"``, ``"icon.png"``]
imageNames.flatMap{ UIImage(named: $0) }

那么 flatMap 是如何实现过滤掉 nil 值的呢? 我们还是来看一下源码:

extension Sequence {// ...
public func flatMap(
@noescape transform: (${GElement}) throws -> T?
) rethrows -> [T] {var result: [T] = []
for element in self {if let newElement = try transform(element) {result.append(newElement)
}
}
return result
}
}

依然是遍历所有元素,并应用try transform(element)闭包的调用, 但关键一点是,这里面用到了 if let 语句, 对那些只有解包成功的元素,才会添加到结果集中:

if let newElement = try transform(element) {result.append(newElement)
}

这样, 就实现了我们刚才看到的自动去掉 nil 值的效果了。

相关文章:
Swift Map详解.

Swift flatMap详解相关推荐

  1. iOS swift 蓝牙详解(蓝牙中心demo,蓝牙外设demo(可替代mac蓝牙串口调试工具),蓝牙中心框架,gif演示)

    持续更新中... 文章目录 1.gif演示 1.1 蓝牙中心app 1.2 蓝牙外设app(外设被一个设备连接后,还可以被另一个设备连接,但两个同时连会导致连接不稳定,容易断开) 1.3 写write ...

  2. swift 枚举详解

    参考博客:http://c.biancheng.net/cpp/html/2426.html 参考官方文档:https://developer.apple.com/library/ios/docume ...

  3. 生怕认可java+flatmap,RxJava 操作符flatMap 与 concatMap详解

    本文独家发布到公众号:Android技术杂货铺 封面图-pixabay 近两年来,RxJava可以说是异常的火爆,受到众多开发者的追捧与青睐,虽然后入门的门槛较高,学习成本较大,但是还是掀起一场学习R ...

  4. RxJava flatMap操作符用法详解

    RxJava系列文章目录导读: 一.RxJava create操作符的用法和源码分析 二.RxJava map操作符用法详解 三.RxJava flatMap操作符用法详解 四.RxJava conc ...

  5. swift. 扩展类添加属性_swift中的声明关键字详解

    原起 学习swift,swift中的关键字当然要了解清楚了,最近在网上看到了关于声明关键字的文章,整理记录一下. 关键字是类似于标识符的保留字符序列,除非用重音符号(`)将其括起来,否则不能用作标识符 ...

  6. Swift 中的Closures(闭包)详解

    Swift 中的Closures(闭包)详解 在Swift没有发布之前,所有人使用OC语言编写Cocoa上的程序,而其中经常被人们讨论的其中之一 -- Block 一直备受大家的喜爱.在Swift中, ...

  7. iOS核心动画详解swift版----基础动画

    2019独角兽企业重金招聘Python工程师标准>>> iOS核心动画详解swift版---基础动画 创建工程,添加2个ViewController,通过rootViewContro ...

  8. Swift - SwiftyJSON的使用详解(附样例,用于JSON数据处理)

    转自:http://www.hangge.com/blog/cache/detail_968.html Swift - SwiftyJSON的使用详解(附样例,用于JSON数据处理) 2016-01- ...

  9. Scala系列8:函数式编程之map,flatten,flatmap的使用详解

    0.Scala函数式编程 我们将来使用Spark/Flink的大量业务代码都会使用到函数式编程.下面这些事开发中常用的函数式编程.注意这些函数都是操作 Scala 集合的,一般会进行两类操作:转换操作 ...

最新文章

  1. Unity3D开发——LeRunning的人物角色信息的显示
  2. java io工作机制_深入分析Java I/O 工作机制
  3. cs架构用什么语言开发_C、C++、Go 语言、Linux服务器开发高级架构师进阶之路
  4. css3中的 @Keyframes
  5. 我总结的几种简单的调用Com组件的方法
  6. JavaScript - 动态数据
  7. 解决 SpringBoot 在 JDK8 中 LocalDateTime (反)序列化问题
  8. 绝佳时机,前所未遇,让艰巨作业全自动化
  9. 马尔可夫决策过程(MDP)
  10. ios -- 极光推送《2》--极光推送消息推送成功,但是手机收不到的解决方法
  11. k8s重要概念及部署k8s集群
  12. 深入浅出数据分析 Head First Data Analysis Code 一书中的文档下载
  13. Facebook 数字货币:缘起、意义和后果
  14. 如何给Layout文件夹分类
  15. AQS抽象队列同步器
  16. JRebel-JVMTI [FATAL] Please make sure that ‘C:\Users\\AppData\Roaming\JetBrains\IntelliJIdea2020.
  17. 设计模式-原型模式C++
  18. 8086CPU从功能上分为几部分?各部分由什么组成?各部分的功能是什么?
  19. 模拟CMOS集成电路设计入门学习(13)
  20. 基于FPGA的DHT11加湿器控制

热门文章

  1. android 来电拒接_android-如何拒绝/关闭特定的来电号码
  2. 计算机要通过手机传播,教你一个手机电脑互传功能的小妙招,比蓝牙传输快千倍...
  3. excel多个窗口独立显示_设置excel工作表打印区域的下技巧
  4. 如何把查询出的结果按姓氏笔画排序
  5. 在王者荣耀角度下分析面向对象程序设计B中23种设计模式之代理模式
  6. iOS图片拉伸(resizableImage)
  7. 追光几何(EverCraft) 与大疆教育 RoboMaster 建立合作伙伴关系
  8. C#-Selenium爬虫抓取(一)
  9. Redis基础都不会,好意思出去面试?
  10. 互联网实名制部分实施,网民大多心存芥蒂