每天了解一点不一样的 Swift 小知识
代码截图
小笔记
这段代码在说什么
截图里 BEFORE 和 AFTER 在代码逻辑上完全一致,只是使用了两种不同的编码风格。前一种使用了常见的可选绑定,方法调用等手段,而后一种仅仅通过使用高阶函数就完成了所有的功能。
Sequence 里的 map, flatMap 和 compactMap
在开始话题之前,我们不妨先看看这三个函数在 Sequence 里的定义,
public func map<T>(_ transform: (Element) -> T) -> [T]
public func flatMap<SegmentOfResult>(_ transform: (Element) -> SegmentOfResult) -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
public func compactMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult]
乍一看,它们都是接受一个名为 transform 闭包作为参数并且整个方法的返回值是一个数组。但仔细一看,这两个关键点在细节上又有着细微的不同。
map
map 对 Sequence 元素进行某种规则的转换,例如:
let arr = [1, 2, 4]
// arr = [1, 2, 4]
let stringArr = arr.map {
"No." + String($0)
}
// stringArr = ["No.1", "No.2", "No.4"]
flatMap
flatmap 里第一个函数闭包的定义是 (Element) -> SegmentOfResult
,并且这里 SegmentOfResult
被定义成 SegmentOfResult : Sequence
,所以它是接受一个数组元素,然后输出一个 SequenceType
类型的元素的闭包。有趣的是,flatMap
最终执行的结果并不是 SequenceType
数组,而是 Sequence
内部元素组成的数组,即 SegmentOfResult.Element
,可能文字读起来有点绕,我们来一段代码:
let arr = [[1, 2, 3], [6, 5, 4]]
let flatArr = arr.flatMap {
$0
}
// flatArr = [1, 2, 3, 6, 5, 4]
在这个例子中,数组 arr 调用 flatMap
时,元素 [1, 2, 3]
和 [6, 5, 4]
分别被传入闭包中,又直接被作为结果返回。但是,最终的结果中,却是由这两个数组中的元素共同组成的新数组:[1, 2, 3, 6, 5, 4]
。
compactMap
如果在 Sequence
里仔细查看的话,我们还可以看到一个已经标注为废弃的同名 flatMap
的 API,它的替代者就是我们马上要介绍的 compactMap
。
Swift 4.1 之前存在 2 个两个
flatMap
函数,虽然它们都是用来降维的,但其中一个除了flat
之外其实还有filter
的作用,在使用时容易产生歧义,所以社区认为最好把第二个版本重新拆分出来,使用一个新的方法命名,就产生了这个提案 SE-0187。 最初这个提案用了filterMap
这个名字,但后来经过讨论,就决定参考了 Ruby 的Array::compact
方法,使用compactMap
这个名字
public func flatMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult]
在这个函数里,闭包的定义变成了:(Element) -> ElementOfResult?
,返回值 ElementOfResult
不像 flatMap
那样要求是一个数组了,而变成了一个 Optional 的任意类型。而 compactMap
最终输出的数组结果,其实不是这个 ElementOfResult?
类型,而是这个 ElementOfResult?
类型解包之后,不为 .None
的元数数组:[ElementOfResult]
。
用代码来总结一下它的功能:
let arr: [Int?] = [1, 2, nil, 4, nil, 5]
let intArr = arr.flatMap { $0 }
// intArr = [1, 2, 4, 5]
Optional 里的 map, flatMap
除了在 Sequence
协议下里使用 map
,flatMap
,在 Optional
里我们也能见到 map
,flatMap
的身影。
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
case None
case Some(Wrapped)
public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
}
所以,对于一个 Optional
的变量来说,map
方法允许它再次修改自己的值,并且不必关心自己是否为 .None
。例如:
let a1: Int? = 3
let b1 = a1.map{ $0 * 2 }
// b1 = 6
let a2: Int? = nil
let b2 = a2.map{ $0 * 2 }
// b2 = nil
相比于 map
而言,flatMap
能够处理闭包参数可能返回 nil
的情况。 例如:
let s: String? = "abc"
let v = s.flatMap { (a: String) -> Int? in
return Int(a)
}
如何选择
在 Sequence 类型中,存在map,flatMap 和 compact 三种转换方法
map
可以将 Sequence 里的元素进行一次类型转换flatMap
等价于先 map 再 flatten(即数组降维)compact
用于去掉结果中的 nil
在 Optional 类型里,存在map和flatMap
- 当我们的输入是一个 Optional,同时我们需要在逻辑中处理这个 Optional 是否为 nil 时,就适合用 map 来替代原来的写法,使得代码更加简短。
- 当我们的闭包参数有可能返回 nil 的时候,就可以使用 Optional 的
flatMap
方法。