前言
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  | (swift, deprecated: 4.1, renamed: "compactMap(_:)", message: "Please use compactMap(_:) for the case where closure returns an optional value")  | 
下面来看下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函数的实现结果了。
结语
将这个三个函数剖析之后更加利于我们对于函数的理解,在实际场景中也能更好地根据对应情况使用不同的函数。