美文网首页ReportsSwiftiOS
flatMap 温顾知新 —— 参照 Swift 源码实现讲解

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

作者: NinthDay | 来源:发表于2017-07-10 00:08 被阅读1811次

    0. 前言

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

    1.png 2.png 3.png 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<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 方法

    这里存在几种情况:

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

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

    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>() 中的 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"])
    

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

    相关文章

      网友评论

      • Waychen:其实关于集合类型,直接去到 Sequence 里面,代码里直接都有栗子的。。。。。。
        any_where:关于3.1的例子,小于2才是不符合预期的,大于2是想要的东西
        Waychen:@NinthDay 32个赞哟,嘿嘿
        NinthDay:恩 ,不过当时用Playground写,压根jump不到定义:joy:,直接拿swift源码看内部实现接口了。另外文章我自认为不是以讲接口为目的,而是在于理解为什么这么做——好像有点自夸了:grin:

      本文标题:flatMap 温顾知新 —— 参照 Swift 源码实现讲解

      本文链接:https://www.haomeiwen.com/subject/bqyehxtx.html