0. 前言
非常感谢喵神对本文的指正,并且引入“降维”一说。对于 Optional
调用 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 可选类型调用 flatMap
方法
flatMap
方法当你对一个可选类型调用 flatMap
的时候,你可以看到 Swift 实际提供了如下例程供我们参考:
let optionalInt : Int? = Optional(3)
let result1 = optionalInt.flatMap { (wrapped) -> String? in
return "\(wrapped)"
}
print(result1) /// Optional("3")
注意:flatMap
应用在可选类型上时,返回类型同样是可选类型 Optional
, 而 wrapped
是什么呢,它是 optionalInt
解包后的值,等同于 optionalInt!
—— 当然前提是 optionalInt
不为 nil
。
这么讲解,可能无法让你理解,甚至混淆你之前的概念,所以我下面会给出 swift 源码实现,加深大家对 flatMap
的理解,我个人倾向于在学习某个知识点时,尽量深入一些,不要停留在表面的使用 —— 当然越深入所需要花费的精力和时间就越多。
ok,继续可选类型下flatMap
swift 源码的实现:
public func flatMap(_ 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
) ->[1,2,3,4]
。数组中的元素均不为nil
; -
[Int?]
类型(即Array
) ->[1,2,nil,4]
。数组中的元素允许存在nil
; - 如果混合可选类型的话,还会衍生出
[Int]?
和[Int?]?
两种情况;
这里我们会先分析 1 和 2,是时候先来看看 Sequence 中 flatMap
的实现:
public func flatMap(
_ transform: @escaping (Elements.Element) -> ElementOfResult?) -> LazyMapSequence>, 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
—— 也就是 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 所以这里的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
,因此 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 所以这里的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>([1,2,3])
let result5 = optionalWrappedArray.flatMap { (array) -> Array? 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>([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"])
最后我希望大家思考下为什么最后四个返回值类型都是可选类型?我们日常开发希望得到的是可选类型中的值,那么又该如何做呢?