背景
当我知道Optional
也有flatMap
方法的时候我的内心是很惊讶的. 之前Collection
的flatMap
就因为两个重载方法搞得我晕了一阵.而且因为不常用对所以对flatMap
一直没有一个概念. 最近一些对可选值的使用场景让我调研并使用了一下, 结合唐巧的一篇博客, 算是对flatMap
这个关键词有了统一的认识.
使用场景
假设我们要设计一个函数, 函数参数是一个image的data, 我们要把它先转换为UIImage
, 然后将其转换为jpg
格式,压缩0.3倍, 然后返回压缩后的UIImage
. 常规的话我们可能会这么写.
func getCompressImage(by data: Data?) -> UIImage? {
guard let data = data,
let image = UIImage(data: data),
let jpegImageData = image.jpegData(compressionQuality: 0.3),
let compressImage = UIImage(data: jpegImageData) else {
return nil
}
return compressImage
}
这样其实已经算是比较清晰了(比起if let)
如果使用flatMap
会是这样的
func getCompressImage(by data: Data?) -> UIImage? {
return data.flatMap {
UIImage(data: $0)
}.flatMap{
$0.jpegData(compressionQuality: 0.3)
}.flatMap{
UIImage(data: $0)
}
}
flatMap
函数的核心思想是对一个容器的元素进行再变形,变形为新的容器, 这里的容器就是指Optional
, 所以我们可以看到它可以将异常情况nil
进行下沉. 在一步步的操作中我们并没有进行解包操作, 只是将有值得情况进行处理, nil
的情况向下传递下去. 代码其实更清晰简洁了.
并且不是类似的情况都能用guard let
解决, 如果其中一个操作比较复杂,要进行
这样还不能足够表现flatMap
的好处. 先看一段传统写法的代码
func getValue(with stringValue: String) -> String {
guard let number = Int(stringValue), number > 100 else {
return "default"
}
let str = String(number + 100)
if str == "1000" {
return str
} else {
return "default"
}
}
为了举例子写了一段没有具体需求的函数, 目的是为了让它无法简化多少.
我们看到在两种为nil
的情况下我们写了两次return "default"
但是使用flatMap
func getValue(with stringValue: String) -> String {
return Int(stringValue).flatMap {
return $0 > 100 ? nil : String($0 + 100)
}.flatMap {
$0 == "1000" ? "1000" : nil
} ?? "default"
}
我们将两种可能为nil
的情况传递到函数调用的末尾, 统一来返回default
, 并且变化的过程看起来更简单清晰了
你可以试一下如何优化第一种代码.. 我是不知道它如何能做到一次处理返回default
总结
flatMap
虽然传的是一个函数, 里面可以做额外的操作, 但是我们最好只做变形操作.不能引发副作用是函数式编程的原则..
另外Optional
的这个函数虽然看起来挺酷, 但是使用不当也会增加阅读的复杂度,一个guard let
就能解决的话还用个函数那就有些炫技的嫌疑了.
对于flatMap
的nil下沉我觉得是与其它解包方式最大的区别. 大家也可以多试试.
flatMap
与map
区别
在函数式编程里所有的集合类型都有这两个方法,
除了集合类型, RxSwift里的Observable
,和标准库里的Optional
也有这两个方法, 它们的共性是对某一类型的元素进行了封装.
我们先将这个封装了之后的类型叫做容器
, 被封装的值叫做元素
在集合类型里的元素很好理解, Optional
容器里的元素就是someValue
, Observable
容器的元素就是观察的值
map
和flatMap
都是容器将元素进行变形得到一个新的容器, 不同的是
map
的变形是 元素
-> 元素
flatMap
的变形是元素
->容器
套用到前面的几种容器类型就发现确实如此.... 主要区别在于transform
函数的类型.