flatMap 温顾知新 —— 参照 Swift 源码实现讲解

0. 前言

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

flatMap 温顾知新 —— 参照 Swift 源码实现讲解_第1张图片
1.png
flatMap 温顾知新 —— 参照 Swift 源码实现讲解_第2张图片
2.png
flatMap 温顾知新 —— 参照 Swift 源码实现讲解_第3张图片
3.png
flatMap 温顾知新 —— 参照 Swift 源码实现讲解_第4张图片
4.png
5.png

1. 为什么写这篇文章

学习 swift talk #01 Networking,我发现 Chris Eidhof 在代码中频繁使用 flatMap 来处理回调的数据,就像 data.flatMap —— data 是可选类型。网上有关于 flatMap 各式各样的教程,告诉如何调用,调用结果会是什么,一般总结是:flatMap可以应用于元素为nil的数组,最后处理得到的返回数组也将是剔除 nil 的结果。

至于写这篇文章的目的:

  1. 大部分教程已经离我们太“久远”
  2. 大部分教程并没有全面讲解 flatMap 的使用,基本都是围绕数组展开,而本文则提供了众多场景来告知为何应该这么用以及举一反三;
  3. 面试时经常问 mapflatMap 的区别或 flatMap 的作用,其实就是两个字“降维”(引自喵神的swift 100 tips);
  4. 周末写篇博客是我的计划之一

2. Optional 可选类型调用 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 方法

这里存在几种情况:

  1. [Int] 类型(即Array) -> [1,2,3,4]。数组中的元素均不为 nil
  2. [Int?] 类型(即Array) -> [1,2,nil,4]。数组中的元素允许存在 nil
  3. 如果混合可选类型的话,还会衍生出 [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! }
 }

定义看起来有点让人“瘆得慌”,尤其是返回类型!不过这里我建议你只需要关注两点:

  1. transform 的类型 (Elements.Element) -> ElementOfResult?,传入参数类型 Elements.Element 由数组中的元素类型决定,比如 [Int] 数组中 Element 就是 Int;而 [Int?] 数组中 Element 就是可选类型 Int?,至于 ElementOfResult 泛型,这取决你。
  2. 内部实现,其实就是间接调用了 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"])

最后我希望大家思考下为什么最后四个返回值类型都是可选类型?我们日常开发希望得到的是可选类型中的值,那么又该如何做呢?

你可能感兴趣的:(flatMap 温顾知新 —— 参照 Swift 源码实现讲解)