0. 前言
非常感谢喵神对本文的指正,并且引入“降维”一说。对于 Optional<T> 调用 flatMap 方法,源码实现内部首先进行解包行为后传值到闭包中(见图),这里可视为“降维”,当然我觉得应该侧重“map”多一些;而对于sequence来说,调用flatMap是否存在“降维”取决于具体处理,至于过滤nil,那不过是 flatMap的内部实现罢了。就像下文中我说到的,侧重点在于flatmap中的“map”过程,由什么到什么的转换,拿Sequence为例,前面的什么已经由数组元素决定了,后者由你决定,你可以把一个Int类型变成[Int],也可以把[Int] 变成Int,完全看你心情。





1. 为什么写这篇文章
学习 swift talk #01 Networking,我发现 Chris Eidhof 在代码中频繁使用 flatMap 来处理回调的数据,就像 data.flatMap —— data 是可选类型。网上有关于 flatMap 各式各样的教程,告诉如何调用,调用结果会是什么,一般总结是:flatMap可以应用于元素为nil的数组,最后处理得到的返回数组也将是剔除 nil 的结果。
至于写这篇文章的目的:
- 大部分教程已经离我们太“久远”
- 大部分教程并没有全面讲解 flatMap 的使用,基本都是围绕数组展开,而本文则提供了众多场景来告知为何应该这么用以及举一反三;
- 面试时经常问
map和flatMap的区别或flatMap的作用,其实就是两个字“降维”(引自喵神的swift 100 tips); - 周末写篇博客是我的计划之一
2. Optional<T> 可选类型调用 flatMap 方法
当你对一个可选类型调用 flatMap 的时候,你可以看到 Swift 实际提供了如下例程供我们参考:
let optionalInt : Int? = Optional<Int>(3)
let result1 = optionalInt.flatMap { (wrapped) -> String? in
return "\(wrapped)"
}
print(result1) /// Optional("3")
注意:flatMap 应用在可选类型上时,返回类型同样是可选类型 Optional<T>, 而 wrapped 是什么呢,它是 optionalInt 解包后的值,等同于 optionalInt! —— 当然前提是 optionalInt 不为 nil。
这么讲解,可能无法让你理解,甚至混淆你之前的概念,所以我下面会给出 swift 源码实现,加深大家对 flatMap 的理解,我个人倾向于在学习某个知识点时,尽量深入一些,不要停留在表面的使用 —— 当然越深入所需要花费的精力和时间就越多。
ok,继续可选类型下flatMap swift 源码的实现:
public func flatMap<U>(_ transform: (Wrapped) throws -> U? ) rethrows -> U? {
switch self {
case .some(let y):
return try transform(y)
case .none:
return .none
}
}
可选类型的概念:要么值不存在 .none(nil),要么有值 case .some(let y),这里 y 就是解包后的值,然后传入 tranform 闭包中,当然闭包处理结果也是有可能返回 nil 的,这取决于你的处理方式了,这也是为什么 tranform 闭包类型为 (Wrapped) throws -> U?,同时 flatMap 返回值类型也是可选类型U?。
知识点:对于可选类型来说,Wrapped 一个值是一个“升维”操作,而对可选类型进行 UnWrapped 操作,是一个“降维”操作,请参照源码。
3. Sequence 调用 flatMap 方法
这里存在几种情况:
-
[Int]类型(即Array<T>) ->[1,2,3,4]。数组中的元素均不为nil; -
[Int?]类型(即Array<T?>) ->[1,2,nil,4]。数组中的元素允许存在nil; - 如果混合可选类型的话,还会衍生出
[Int]?和[Int?]?两种情况;
这里我们会先分析 1 和 2,是时候先来看看 Sequence 中 flatMap 的实现:
public func flatMap<ElementOfResult>(
_ transform: @escaping (Elements.Element) -> ElementOfResult?) -> LazyMapSequence<LazyFilterSequence<LazyMapSequence<Elements, ElementOfResult?>>, ElementOfResult > {
return self.map(transform).filter { $0 != nil }.map { $0! }
}
定义看起来有点让人“瘆得慌”,尤其是返回类型!不过这里我建议你只需要关注两点:
- transform 的类型
(Elements.Element) -> ElementOfResult?,传入参数类型Elements.Element由数组中的元素类型决定,比如[Int]数组中Element就是Int;而[Int?]数组中Element就是可选类型Int?,至于ElementOfResult泛型,这取决你。 - 内部实现,其实就是间接调用了
map方法,我个人很喜欢链式调用,实在是太酷了,就像self.map(transform).filter { $0 != nil }.map { $0! },理解起来也很简单,往map函数中传入闭包tranform对每个元素做处理,然后结果值调用filter剔除值为nil的元素,最后调用map依次对结果中的元素做解包处理——要知道此时数组中可不存在nil值了,请大胆放心的解包吧。
至此,部分同学应该会对上述两点产生一些不解或疑惑,可能会问一些问题:
Q1: 调用 self.map(transform),那么 transform 传入参数 Elements.Element 是什么类型?
A1: 再次强调,Elements.Element是由数组元素类型决定。
Q2:怎么理解“至于 ElementOfResult 泛型,这取决你。”
A2:Elements.Element 类型是由数组类型决定的,而我们希望数组中每个元素应用 transform 闭包后的结果值类型是由我们决定的,比如我们希望是字符串类型,那么实际代码调用的时候用 String 替换 ElementOfResult,接着 transform 中的 ElementOfResult 又决定了 func flatMap<ElementOfResult>() 中的 ElementOfResult —— 也就是 String 类型。
3.1 [Int] 类型
例程:
let noneOptionalArray = [1,2,3,4]
let result2 = noneOptionalArray.flatMap { (x) -> Int? in
guard x > 2 else {
return nil // 小于 2 的情况认为是不符合预期 返回 nil,其他情况进行加一操作,因此返回值类型为 `Int?`
}
return x+1
}
print(result2)//[ 4, 5]
前面说到 transform 闭包类型中的 Elements.Element 是由数组元素类型决定,所以这里 x 的类型为 Int,此外我们的 transform 希望对每个元素做 +1 处理 —— 那么还是个 Int 类型,所以我们将 ElementOfResult 替换成 Int,当然如果你想数组元素格式化成字符串,那么这里返回值类型就是 String?;参考源码我们知道数组应用了 transform 之后会调用 filter 剔除值为 nil 的元素,剩下的都是 Optional 中的 .some,所以最后一步就是解包 map{ $0!}。
学习过程中,我更改了闭包中实现——不再设定大于2就返回nil的处理,显然这没有任何问题:
let noneOptionalArray = [1,2,3,4]
let result2 = noneOptionalArray.flatMap { (x) -> Int? in
return x+1
}
print(result2)//[2, 3, 4, 5]
接着我又在想,既然闭包不可能返回 nil,那返回 Int? 可选类型干嘛,应该返回 Int 也是Ok的吧,于是我又修改了代码:
let noneOptionalArray = [1,2,3,4]
let result3 = noneOptionalArray.flatMap { (x) -> Int in
return x+1
}
print(result3)//[2, 3, 4, 5]
这也是Ok的,但是倘若你在闭包处理中加会那段大于2返回nil的限制代码,Xcode会立即提示你的返回值类型错误,这也是我上面说到的,ElementOfResult 的类型由你决定。
3.2 [Int?] 类型
讲完 [Int] 类型,本节实际上就没有任何难度了,这里给出几个例程
let optionalArray = [1,nil,2,3]
// 由于optionalArray里面的类型是 Optional<Int> 所以这里的x也是可选类型
let result4 = optionalArray.flatMap { (x) -> Int? in
guard let xx = x else { return nil }
return xx + 1
}
print(result4)//[2, 3, 4]
optionalArray 的类型为 Array<Int?>,因此 x 的类型为 Int?,闭包接收到的元素分别为.some(1),.none,.some(2).some(3)可选类型,这也是为什么闭包处理中首先对 x 进行绑定解包,如果 x 为 nil,直接返回 nil,否则进行+1操作。
当然3.1小节中的好奇我同样带到了这里,我一定要返回 Int?吗? 对于不喜欢的 nil 我希望返回 0 就ok拉。
let optionalArray = [1,nil,2,3]
// 由于optionalArray里面的类型是 Optional<Int> 所以这里的x也是可选类型
let result5 = optionalArray.flatMap { (x) -> Int in
guard let xx = x else { return 0 }
return xx + 1
}
print(result5)//[2, 0, 3, 4]
由此可以看到 tranform 传入 x 的类型我们无法左右,但是!!闭包返回值类型我们却可以随心所欲的改变,这一切取决于你。
4. 可选类型混合Sequence
有了上面的铺垫,下面相对来说会顺风顺水一些,先来看 [Int]? 类型:
let optionalWrappedArray = Optional<Array<Int>>([1,2,3])
let result5 = optionalWrappedArray.flatMap { (array) -> Array<String>? in // 1
return array.map({ (element) -> String in // 2
return "element:\(element)"
})
}
print(result5) // Optional(["element:1", "element:2", "element:3"])
首先 optionalWrappedArray 整体来看是一个可选类型,要么没有值 nil,要么有值是一个数组,而调用 flatMap 后返回值类型同样是一个可选类型;注意 1 中的 array,有了源码的讲解,我们知道这里 array 是解包后的值,也就是 [1,2,3],接着我们调用 map 方法将元素格式化成字符串输出。
如果你跟随我的节奏码代码,你应该注意到输入 optionalWrappedArray. 智能提示会有很多方法,你可以会不小心选择到 flatMap(<#T##transform: (Int) throws -> ElementOfResult?##(Int) throws -> ElementOfResult?#>) 方法,然后调用方式是这样了:
let result6 = optionalWrappedArray?.flatMap({ (x) -> String? in
return "element:\(x + 1)"
})
print(result6)// Optional(["element:2", "element:3", "element:4"])
这里你需要仔细观察两者的不同,相信你的火眼金睛 —— 这么大的一个问好 ? 应该已经注意到了吧!对于 optionalWrappedArray? 已经产生了一个解包行为,实际调用等价于 [1,2,3].flatMap (而对于 nil.flatMap 返回自然是 nil 喽)这又回到了小节 3.1 的内容,不妨自己回顾下,理下思路。
最后我们说说 [Int?]? 类型来结束本文:
let doubleOptional = Optional<Array<Int?>>([1,nil,2,3])
let result7 = doubleOptional.flatMap { (array:[Int?]) -> String? in
return array.reduce("", { (res:String, number:Int?) -> String in
guard let num = number else { return res + " number:null" }
return res + " number:\(num)"
})
}
print(result7)// Optional(" number:1 number:null number:2 number:3")
以及提前解包然后调用 flatMap
let result8 = doubleOptional?.flatMap({ (x) -> String? in
guard let element = x else { return nil}
return "element \(element * element)"
})
print(result8)//Optional(["element 1", "element 4", "element 9"])
最后我希望大家思考下为什么最后四个返回值类型都是可选类型?我们日常开发希望得到的是可选类型中的值,那么又该如何做呢?
