前言
map
是Swift中常用的函数,同时它还有两个兄弟级函数flatMap
和compactMap
,下面就根据官方声明和源码来谈谈这个三个函数之间的联系和区别,加以深刻记忆。
map
首先是map
函数的使用:
1 | let nums = [1, 2, 3] |
map
函数接受一个闭包函数作为参数,他会遍历整个序列元素,并对每个元素执行闭包函数中定义的操作。
然后我们再看下map
函数的官方声明:
1 | func map<T>(_ transform: (Value) throws -> T) rethrows -> [T] |
我们来稍微解释下这个声明的意思,所需参数transform
的类型很明显是一个闭包类型:将(Value)
(序列元素的类型)转换为类型T
,最终返回一个T
类型的序列。
这就是map
的初步了解,我们再看下一个。
flatMap
flatMap
的声明和使用有两种,让我们分别来讲解一下。
先看第一种使用方法:
1 | let nums = [[1, 2], [3, 4]] |
这样我们就看出map
和flatMap
的区别了。
我们看下map
函数的官方声明:
1 | func flatMap<SegmentOfResult>(_ transform: (Value) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence |
这个声明比map
相比就长了很多,但我们可以将它转化一下:
1 | func flatMap<T>(_ transform: (Value) throws -> T) rethrows -> [T.Element] where T : Sequence |
这么看其实就已经和map
的声明差不多了,同时不同的地方表现在以下几个方面:T
的类型有了约束,必须是继承Sequence
协议,因为它的返回值也变成了[T.Element]
,这个地方的关键点就是造成与map
函数不同的地方。
然后我们看下flatMap
源码部分:
1 | extension LazySequenceProtocol { |
我们通过源码可以看出,flatMap
函数本身也是调用了map
函数,同时进行了joined
操作,joined
操作可以将序列包裹序列的元素重新组合成一个序列(只能拆第二层的序列,第三层的依旧是原类型),所以这就是flatMap
函数的不同点。
flatMap
函数的另一种使用方式其实已经弃用,功能已经划分给compactMap
函数,但我们可以看下它的声明:
1 | 4.1, renamed: "compactMap(_:)", message: "Please use compactMap(_:) for the case where closure returns an optional value") (swift, deprecated: |
下面来看下compactMap
函数。
compactMap
使用方法:
1 | let nums = [1, 2, nil, 3, 4] |
如果使用map
函数实现上面代码,会直接报错无法运行,但是compactMap
函数不会报错,还会把nil
剔除,同时可以将可选类型解包。这在实际场景中是个非常有用的机制,可以用条件闭包将非法或无效的内容过滤。
然后我们先来看下compactMap
的官方声明:
1 | func compactMap<ElementOfResult>(_ transform: (Value) throws -> ElementOfResult?) rethrows -> [ElementOfResult] |
将它转化一下:
1 | func compactMap<T>(_ transform: (Value) throws -> T?) rethrows -> [T] |
和map
函数不同地方就在于transform
闭包参数的返回值是T
的可选类型,最终的返回结果是解包后的[T]
。
我们在根据源码分析下它是如何筛选并解包的:
1 | extension LazySequenceProtocol { |
非常容易理解,同样是调用了map
函数,并调用filter
函数进行非空筛选,最终进行解包。这也就不难理解compactMap
函数的实现结果了。
结语
将这个三个函数剖析之后更加利于我们对于函数的理解,在实际场景中也能更好地根据对应情况使用不同的函数。