Swift 中提供了很多的高阶函数,而其中map
,flatMap
与 compactMap
又有许多的相似之处,经常对他们使用的时机有些模糊,今天我们就从标准库实现的角度来看下他们各自的区别以及适用的情况。
Overview
关于它们的定义:
1 | // Sequence |
只是从上面的函数签名来看这三个函数,它们虽然都接受一个 transform
的闭包作为参数,且闭包捕获的参数都为 Sequence
中的元素类型,但是闭包所返回的类型可以与 Sequence
元素的类型不同。map
中的闭包返回类型为 T
,而 map
函数的返回类型为 [T]
。
flatMap
中的闭包的参数同样是 Sequence
中的元素类型,但其返回类型为 SegmentOfResult
。在函数体的范型定义中,SegmentOfResult
的类型其实就是是 Sequence
。 而flatMap
函数返回的类型是 SegmentOfResult.Element
的数组。从函数的返回值来看,与 map
的区别在于 flatMap
会将 Sequence
中的元素进行”降温”,返回的类型会是 Sequence
中元素类型的数组,而 map
返回的这是闭包返回类型T
的数组。
compactMap
闭包参数的返回类型为 ElementOfResult?
, 而函数返回类型为 [ElementOfResult]
。因此,compactMap 会将闭包返回的 Optional 的类型解包并返回解包后的数组。
下面分别从源码角度再看下标准库的具体实现方式:
map
1 | public func map<T>( |
在上面 map 的实现中,主要做了以下几件事:
- 首先根据序列中的
underestimatedCount
和变换的目标类型T
,初始化一个result
的数组来存放结果。 - 通过
iterator
遍历序列中的每一个元素,并调用transform
闭包对元素进行变换。 - 将把变换后的结果保存结果数组中。
因此,经过 map
变换的 Sequence
就不再是一个简单的序列了,而是一个数组。我们只能对有限序列使用map进行变换。
ContiguousArray
和名字所暗示的不同,它其实是 Swift 中最简单的数组类型。相比标准的数组,它可以有更好的性能表现,而即便没有,也至少可以提供与Array
相同性能水平的表现。同时也暴露出相同的接口。
flatMap
1 | public func flatMap<SegmentOfResult : Sequence>( |
对于 flatMap 的实现,我们可以看出,它做了以下几件事情:
- 初始化一个名为
result
的新数组,用于存放结果。 - 遍历自己的元素,对于每个元素,调用闭包的转换函数
transform
进行转换。 - 将转换的结果,使用
appendContentsOf
方法,将结果放入result
数组中。
而这个 appendContentsOf 方法,即是把数组中的元素取出来,放入新数组。
compactMap
1 | public func compactMap<ElementOfResult>( |
而compactMap
的实现则做了以下事情:
- 构造一个名为
result
的新数组,用于存放结果。 - 遍历自己的元素,对于每个元素,调用闭包的闭包参数
transform
,进行转换。 - 将转换的结果进行解包,如果有值则使用
append
方法,将结果放入result
数组中。
所以,该 compactMap
函数可以过滤闭包执行结果为 nil
的情况,仅收集那些转换后非空的结果。
如何选择他们使用的时机呢?
When to use compactMap
当转换闭包返回可选值并且你期望得到的结果为非可选值的序列时,使用 compactMap
。
compactMap 与 map 的区别参考下面的例子:
1 | let scores = ["1", "2", "three", "four", "5"] |
根据这些不同点,可以归纳出三个函数适用的不同情况,当闭包返回的结果需要是序列类型且期望返回的结果是一维数组时,使用 flatMap
, 而需要过滤 transform
结果的可选值时,使用 compactMap
。
如:
1 | let arr = [[1, 2, 3], [4, 5]] |
When to use flatMap
当对于序列中元素,转换闭包返回的是序列或者集合时,而你期望得到的结果是一维数组时,使用 flatMap
。
1 | let scoresByName = ["Henk": [0, 5, 8], "John": [2, 5, 8]] |
其实
s.flatMap(transform)
的结果等同于Array(s.map(transform).joined())
。
compactMap vs flatMap
当在序列元素上使用转换闭包,且返回值为Optional
类型时,使用 compactMap
, 其他情况下 map
与 flatMap
都可以得到你所想要的结果。